Skip to content

Awkward to write code generic over registers #459

Closed
@couchand

Description

@couchand

I am writing code that is generic over registers, and I'm getting a bit frustrated by the awkward bounds that need to be applied.

The ideal case would look something like this:

fn read_bit<U: Copy, Register: Readable>(reg: &Reg<U, Register>) -> U {
    // ...
}

Unfortunately, the Readable and Writable traits are impl'd on the Reg struct typedef itself, not on the inner type. Also unfortunately, the traits don't provide any functionality directly, so I can't rely completely on the traits and forget about the Reg struct. This means the bounds need to be written in the much more awkward form:

fn read_bit<U: Copy, Register>(reg: &Reg<U, Register>) -> U
where
    Reg<U, Register>: Readable,
{
    // ...
}

In my case, I'd like to apply the bounds to associated types in a trait. Though I can add the relevant where bounds on Reg<...> to the trait definition, they don't "propagate" to uses of that trait, meaning I need to repeat them on any block that uses the trait.

Ideally it would look something like:

trait Port {
    type U: Sized + Copy + BitAnd + BitOr + ...;
    type DDR: Readable + Writable;
    type Data: Readable + Writable;

    fn ddr() -> Reg<Self::U, Self::DDR>;
    fn data() -> Reg<Self::U, Self::Data>;
}

impl<P: Port> Component<P> {
    fn configure() {
        P::ddr().modify(|r, w| {
            // ...
        });
    }

    fn do_something() {
        P::data().modify(|r, w| {
            // ...
        });
    }
}

But, given the above constraints, I actually need to write:

trait Port { // I could put a where clause here but it really wouldn't help
    type U: Sized + Copy + BitAnd + BitOr + ...;
    type DDR;
    type Data;

    fn ddr() -> Reg<Self::U, Self::DDR>;
    fn data() -> Reg<Self::U, Self::Data>;
}

impl<P: Port> Component<P>
where
    Reg<P::U, P::DDR>: Readable + Writable
{
    fn configure() {
        P::ddr().modify(|r, w| {
            // ...
        });
    }
}

impl<P: Port> Component<P>
where
    Reg<P::U, P::Data>: Readable + Writable
{
    fn do_something() {
        P::data().modify(|r, w| {
            // ...
        });
    }
}

I believe that this situation could be resolved by generating the impl Readable/impl Writable for the hidden inner register type rather than directly on the outer register type alias. For documentation purposes, there could be a blanket impl on Reg, like:

impl<U, REG: Readable> Readable for Reg<U, REG> {}
impl<U, REG: Writable> Writable for Reg<U, REG> {}

Which I believe would be allowed due to coherence.

If you are open to these changes, I would be willing to work on a PR implementing them. Or if you have suggestions for another strategy to make this usage more ergonomic, I'm all ears.

I spent a while looking around for related discussion and found none. Feel free to point me to any existing documentation or prior discussion on this topic.

Thanks for all your hard work on this!

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions