Search code examples
rustrust-macros

Refer to generic type of struct in macro


I need to use an attribute of the generic type of a struct in a macro.

A slightly contrived but minimal example would be if I wanted to implement a method for a generic struct, that returned the minimum value of its generic type.

struct Barn<T> {
    hay: T
}

macro_rules! impl_min_hay{
    ($barntype:ident) => {
        impl $barntype {
            fn min_hay(&self) -> ????T {
                ????T::MIN
            }
        }
    }
}

type SmallBarn = Barn<i8>;
type BigBarn = Barn<i64>;

impl_min_hay!(SmallBarn);
impl_min_hay!(BigBarn);

fn main() {
    
    let barn = SmallBarn { hay: 5 };
    println!("{}", barn.min_hay());
}

How would I resolve from SmallBarn to get the generic type and thus it's MIN attribute?

The actual problem I am trying to solve is a change to this macro. The macro is applied to, among others, BooleanChunked, which is defined as: pub type BooleanChunked = ChunkedArray<BooleanType>

And I need to use an attribute of BooleanType


Solution

  • The only general solution I can think of is to define a trait that allows you to get at the type parameter (the syntax for this is <Type as Trait>::AssociatedType):

    trait HasHayType {
        type HayType;
    }
    impl<T> HasHayType for Barn<T> {
        type HayType = T;
    }
    
    macro_rules! impl_min_hay{
        ($barntype:ident) => {
            impl $barntype {
                fn min_hay(&self) -> <$barntype as HasHayType>::HayType {
                    <$barntype as HasHayType>::HayType::MIN
                }
            }
        }
    }
    

    Here's the complete program on play.rust-lang.org.

    That said, once you have a trait, you don't really need the macro – you can just implement min_hay on the trait (this example makes use of the widely used num-traits crate, because this approach needs a trait for "things that have minimum values"):

    use num_traits::Bounded;
    trait HasHayType {
        type HayType: Bounded;
        fn min_hay(&self) -> Self::HayType;
    }
    impl<T: Bounded> HasHayType for Barn<T> {
        type HayType = T;
        fn min_hay(&self) -> T {
            T::min_value()
        }
    }
    

    And here's what that looks like as a complete program.

    (And of course, once you've done that too, you don't really need the separate trait either: you can inline the definition of HasHayType into Barn, using a where clause if you want to be able to handle Barns with non-numerical hay types in addition to the ones where you'd use the macro. Presumably, though, the actual situation you have is more complex than the cut-down example you used for the question, so I gave the more complex versions in case the simplified versions wouldn't work.)

    As a side note, min_hay doesn't actually need the &self parameter here; you could remove it, in order to be able to learn the minimum amount of hay without needing a barn to put it in.