Select field based on generic type (if constexpr)

Say I have the following struct:

struct S<A, B> {
    a: A,
    b: B,
}

I want to write a function that takes in an instance of S and returns one of its fields based on a generic type parameter or const. A non compiling example is below:

struct S<A, B> {
    a: A,
    b: B,
}

impl<A, B> S<A, B> {
    fn select_field<const C: bool>(&self) -> if C {&A} else {&B} {
        if C {
            &self.a
        } else {
            &self.b
        }
    }
}

fn main() {
    let s = S {
        a: 10,
        b: String::from("hello"),
    };
    let a = s.select_field::<true>();
    let b = s.select_field::<false>();
    println!("{}, {}", a, b);
}

In theory the compiler has the information it needs to do something like this where it can compile away the conditional. It is not working though because as far as I know there is no way to express return types as a function of the input types. How can I express this in Rust so that there is no if statement at runtime?

In Rust you have to return a concrete type. In your case, namely an enum or a trait object.

You will need a trait like

trait FieldSelector {
  type Output;
  fn get(&self) -> &Self::Output;
}

and implement it for different types you use in C.

struct FieldA;
struct FieldB;

impl FieldSelector for S<A, B, FieldA> {
   type Output = A;
   fn get(&self) -> &A { &self.a }
}
2 Likes

Put together something here based on using a trait:

struct S<A, B> {
    a: A,
    b: B,
}

trait SelectField<const C: usize> {
    type FieldType;
    fn select_field(&self) -> &Self::FieldType;
}

impl<A, B> SelectField<0> for S<A, B> {
    type FieldType = A;
    fn select_field(&self) -> &Self::FieldType {
        &self.a
    }
}

impl<A, B> SelectField<1> for S<A, B> {
    type FieldType = B;
    fn select_field(&self) -> &Self::FieldType {
        &self.b
    }
}

fn main() {
    let s = S {
        a: 10,
        b: String::from("hello"),
    };
    let a = SelectField::<0>::select_field(&s);
    let b = SelectField::<1>::select_field(&s);
    println!("{}, {}", a, b);
}
2 Likes

rust don't have true dependent types, but for simple scenarios, similar effect can be achieved. for example:

if C { &A } else { &B } looks like a type level function , which can be emulated using associated types:

trait SelectField<const C: bool> {
    type FieldType<'a>;
    //...
}

and if C { &self.a } else { &self.b } can be emulated with associated function (method):

trait SelectField<const C: bool> {
    //...
    fn get(&self) -> Self::FieldType<'_>;
}

so all combined, it looks like this:

trait SelectField<const C: bool> {
    type FieldType<'a>
    where
        Self: 'a;
    fn get(&self) -> Self::FieldType<'_>;
}
impl<A, B> SelectField<true> for S<A, B> {
    type FieldType<'a> = &'a A where Self: 'a;
    fn get(&self) -> &A {
        &self.a
    }
}
impl<A, B> SelectField<false> for S<A, B> {
    type FieldType<'a> = &'a B where Self: 'a;
    fn get(&self) -> &B {
        &self.b
    }
}

impl<A, B> S<A, B> {
    fn select_field<const C: bool>(&self) -> <Self as SelectField<C>>::FieldType<'_>
    where
        Self: SelectField<C>,
    {
        self.get()
    }
}

please note, although it has similar effect, this is not the recommended way to utilize the rust type system. in rust, you should think types as being solved, as in constraint solver, other than being computed, as in reflection, inspection, or meta-programming.

2 Likes

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.