'field selector' generic?

trait HasFields {
  type a;
  type b;
  type c;
  type d;
}

pub struct Foo <T: HasFields, f: FieldSelector> {
  inner: T::f
}

^-- is there a way to do something like this? valid values for f would be a, b, c, d

basically i want to pass in two generics, one is a type which satisfies the trait (we can do this already); another is a "selector" on the trait (I don't know how to do this)

  1. is this possible, if so how? it not, is there a name for this feature

  2. if 'no' in above, are there ways of faking this ?

Is this an XY problem? I can't think of any reason I would want to do this.

Maybe you can fake it with a macro. But I don't know enough about your expected interfaces to provide any more detail than that.

4 Likes

You need to define a separate trait to act as the type level function between a selector type and the field type. (In the future when we have general const generics, the selector could be an enum instead of four separate types.)

pub trait HasFields {
  type A;
  type B;
  type C;
  type D;
}

pub trait Select<F> {
    type Out;
}

pub struct SelA;
pub struct SelB;
pub struct SelC;
pub struct SelD;
impl<T: HasFields> Select<SelA> for T { type Out = T::A; }
impl<T: HasFields> Select<SelB> for T { type Out = T::B; }
impl<T: HasFields> Select<SelC> for T { type Out = T::C; }
impl<T: HasFields> Select<SelD> for T { type Out = T::D; }

pub struct Foo<T: HasFields + Select<F>, F> {
  inner: <T as Select<F>>::Out,
}

Note that you might want to eliminate HasFields entirely, and just have the implementors directly implement Select four times. This might be usefully more flexible and simplify trait bounds, or it might complicate the job of implementors without any benefits, depending on the situation.

2 Likes

Could I trouble you to write the code without HasFields ? The part I don't understand is -- once you remove HasFields, what do you replace T::A, T::B, T::C, T::smiley: with ?

That's hard to do because the generic implementations go away, but it might be like this (supposing that Something would have implemented HasFields):

struct Something {
    a: i32, // I assume that the implementor should in fact have a field…
}
impl Select<SelA> for Something { type Out = i32; }
// ... and more for the other selectors

In the places you would write T::A, you write <T as Select<SelA>>::Out instead; and a T: HasFields bound becomes

T: Select<SelA> + Select<SelB> + Select<SelC> + Select<SelD>

Of course, this is more verbose in each step, but it means the selection doesn't need a trait that lists every type; as I said, it depends on which tradeoff you want. The main point is that HasFields itself it's not actually necessary.