Search code examples
rustdesign-patternsi2cbitflags

Rust trait inheritance for a subset of the trait implementations


I'm not sure how exactly to formulate this question so I will illustrate by example.

I am using the bitflags crate which provides a trait called Flags. The flags trait involves the presence of an associated type, something like

pub trait Flags: Sized + 'static {
    /// The underlying bits type.
    type Bits: Bits;

    fn from_bits(bits: Self::Bits) -> Option<Self> {
        ... 
    }

    ...
}

where the Bits trait is implemented for the following

i8, i16, i32, i64, i128, isize, u8, u16, u32, u64, u128, usize

I then wish to define a trait (Register) which inherits Flags but adds the requirement that an associated constant be defined upon implementation. Something like

trait Register: Flags {
    const ADDRESS: u8;

    fn address(&self) -> u8 {
        Self::ADDRESS
    }
}

Having access to a Flags with an attached address would allow me to do useful things, but some of these useful things rely on the Flags implementation to have the Bits type be u8. I end up in a situation where I have

trait Register: Flags {
    const ADDRESS: u8;

    fn address(&self) -> u8 {
        Self::ADDRESS
    }

    fn do_something(&self) {
        let new_bits_data: u8 = ...;
        *self = Self::from_bits(new_bits_data).unwrap()
    }
}

And calling Self::from_bits() doesn't work because we don't yet know the type of the Bits which a specific implementation of Flags will have.

In my mind I would like to be able to define the Register trait only for the subset of Flags implementations which have set

type Bits = u8;

but I don't know if that is a good idea or even possible.

Do you have any ideas for how I might work around this issue or if it is possible to implement what I want?

I have considered the idea of defining a trait Flags_u8 which is the same as Flags with the constraint that the Bits type is u8. But I don't know how that would look.


Solution

  • You can constrain associated types of super traits similar to what you can do for any trait:

    trait Register: Flags<Bits = u8> { ... }
    

    Or for a specific method:

    trait Register: Flags {
        const ADDRESS: u8;
    
        fn address(&self) -> u8 {
            Self::ADDRESS
        }
    
        fn do_something(&self)
        where
            Self: Flags<Bits = u8>,
        {
            let new_bits_data: u8 = ...;
            *self = Self::from_bits(new_bits_data).unwrap()
        }
    }