Enum-like trait


#1

I’m wondering if anyone has experience with modeling an enum-like type as a trait. I’m working with a radio peripheral that supports pre-configuring up to 8 addresses to be used when sending and receiving data. I want to model this via a trait that can be implemented and is passed as a generic parameter to the radio type:

pub trait Address {
    ...
}

pub struct Radio<A: Address> {
    ...
}

impl<A: Address> Radio<A> {
    fn new() -> Radio<A> { ... }

    fn listen(&mut self, addresses: &[A])
    -> impl Stream<Item = (A, [u8; 25]), Error = !> + '_
    { ... }

    fn send(&mut self, address: A, packet: [u8; 25])
    -> impl Future<Item = (), Error = !> + '_
    { ... }

    ...
}

Internally once configured the radio will only use integers to refer to these addresses, during configuration it needs some more data from the address trait, for simplicities sake lets assume the addresses are just strings:

pub trait Address {
    fn name(&self) -> &str;
    ...
}

My first attempt at making Address represent an enum-like type is simply a pair of constant array and get index function:

pub trait Address: Sized + Copy {
    const ADDRESSES: [Self; 8];

    fn name(&self) -> &str;

    fn index(&self) -> usize;
}

This works, but it doesn’t provide any real compile time guarantees, this could be implemented for a type with more than 8 possible values, and there’s nothing ensuring <A as Address>::ADDRESSES[5].index() == 5 or a.index() < 8.

So, anyone have ideas on a good way to do this? Or should I just give up on letting the user provide their own enum type and just force them to match on my own pub enum Address { One, Two, ... }.


#2

Perhaps instead of having an index be on the trait/supplied by user, your Radio can assign indexes based on the order of addresses given to you by user? Then they supply info that they’re responsible for and you assign your own invariants.


#3

Yeah, that was one option I considered, probably by adding a bound Address: Eq and doing

fn send(&mut self, address: A, packet: [u8; 25])
-> impl Future<Item = (), Error = !> + '_
{
    let index = A::ADDRESSES.iter()
        .position(|a| a == address)
        .expect("Can only use specified addresses");
    ...
}

I think it definitely does improve the compile time safety, but I’m still on the fence whether this provides enough utility over having a pre-specified enum of addresses and passing the configuration in just as some object.


#4

Another option might be to obtain a Sender struct for a given address from your Radio, and send packets through it. This would ensure you only validate the address one time rather than per send operation. You can even embed the address as a type (ie Sender<Address0>, Sender<Address1>, etc up to 7) so you don’t need to store the index as a field.

But yeah, I don’t know if this is overengineering.