Get element value from tuple given the type of that element

My goal is to retrieve an element from a tuple given the type of that element. For my use case, the types within the tuple are expected to be unique. The code I wrote below implements this behavior but I'm not too familiar with rust macros and I'm wondering if there's a "better" way or an existing crate that already does this for me.

struct Index0;
struct Index1;
struct Index2;

trait Element<T, I> {
    fn get(&self) -> &T;
}

impl<T> Element<T, Index0> for T {
    fn get(&self) -> &T {
        self
    }
}

macro_rules! impl_element {
    ($idx:tt, $idx_type:ty, $get:ident; $($tuple:ident),*) => {
        impl<$($tuple),*> Element<$get, $idx_type> for ($($tuple),*)
        where $get: Element<$get, Index0>
        {
            fn get(&self) -> &$get {
                self.$idx.get()
            }
        }
    }
}

impl_element!(0, Index0, A; A, B);
impl_element!(1, Index1, B; A, B);
impl_element!(0, Index0, A; A, B, C);
impl_element!(1, Index1, B; A, B, C);
impl_element!(2, Index2, C; A, B, C);

fn main() {
    let a = (4.1, 2);
    let b = ("foo", false, 5);
    
    let x: &i32 = a.get();
    let y: &bool = b.get();
    let z: &&str = b.get();
    
    assert_eq!(x, &2);
    assert_eq!(y, &false);
    assert_eq!(z, &"foo");
}

Rust Playground Link

I learned some more things about macros and I was able to improve impl_element a bit. Now, it has the interface I was after but its implementation is not the greatest as it doesn't support arbitrary-sized tuples. Another thing I wanted to improve was the Element trait. Preferably, it would only have one type argument (i.e. Element<T>).

struct Index0;
struct Index1;
struct Index2;

trait Element<T, I> {
    fn get(&self) -> &T;
}

macro_rules! impl_element {
    (@impl $($tuple:ident)+; ; $($rem_ty:ty,)*; $($rem_tt:tt)*) => {};
    (@impl $($tuple:ident)+; $head_id:ident $($tail_id:ident)*; $head_ty:ty, $($tail_ty:ty,)*; $head_tt:tt $($tail_tt:tt)*) => {
        impl<$($tuple,)+> Element<$head_id, $head_ty> for ($($tuple,)+) {
            fn get(&self) -> &$head_id {
                &self.$head_tt
            }
        }
        impl_element!(@impl $($tuple)*; $($tail_id)*; $($tail_ty,)*; $($tail_tt)*);
    };
    ($($tuple:ident),+ $(,)*) => {
        impl_element!(@impl
            $($tuple)+ ;
            $($tuple)+ ;
            Index0, Index1, Index2, Index3, Index4, ;
            0 1 2 3 4
        );
    };
}

impl_element!(A, B);
impl_element!(A, B, C);

fn main() {
    let a = (4.1, 2);
    let b = ("foo", false, 5);

    let x: &i32 = a.get();
    let y: &bool = b.get();
    let z: &&str = b.get();

    assert_eq!(x, &2);
    assert_eq!(y, &false);
    assert_eq!(z, &"foo");
}

Rust Playground Link

I believe frunk has what you’re looking for! (Specifically, the HList type, along with pluck and various other things.)

Interesting crate, thanks for the suggestion! I think the HCons::get method along with the Selector trait is pretty close to what I want. In fact, the Selector trait looks fairly similar to my Element trait. However, in my use case, I'm given a tuple and I can't seem to find a way to convert a tuple into an HList without specifying type arguments. Here's my closest attempt to replicate the above code given tuples.

#[macro_use]
extern crate frunk;

fn main() {
    let a: Hlist![f32, i32] = From::from((4.1, 2));
    let b: Hlist![&str, bool, i32] = From::from(("foo", false, 5));

    let x: &i32 = a.get();
    let y: &bool = b.get();
    let z: &&str = b.get();

    assert_eq!(x, &2);
    assert_eq!(y, &false);
    assert_eq!(z, &"foo");
}

Ah, yeah, interesting. Does this help? I'm not sure if it's possible to make it generic to tuple size, but at least you can let the compiler infer the types:

    let a: Hlist![_, _] = From::from((4.1, 2));
    let b: Hlist![_, _, _] = From::from(("foo", false, 5));

    let x: &i32 = a.get();
    let y: &bool = b.get();
    let z: &&str = b.get();

    assert_eq!(x, &2);
    assert_eq!(y, &false);
    assert_eq!(z, &"foo");

It should be possible to turn a tuple into an HList without any type annotation using Generic.

That did it, thanks @ExpHP!

use frunk::generic::Generic;

fn main() {
    let a = Generic::into((4.1, 2));
    let b = Generic::into(("foo", false, 5));

    let x: &i32 = a.get();
    let y: &bool = b.get();
    let z: &&str = b.get();

    assert_eq!(x, &2);
    assert_eq!(y, &false);
    assert_eq!(z, &"foo");
}
2 Likes

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.