Method that checks whether a generic type implements X trait, inside generic function body

Would be nice to have a generic method that checks whether some trait is implemented on that generic method. Here's a use case:

fn generate_random_number_for_arbitrary_type(value: T) -> T {
  if T.implements_trait::<num::Float>() {
    generate_random_number_from_exponent_minus_100_to_plus_100()
  } else {
    generate_random_number_from_exponent_0_to_100()
  }
}

Isn't this just equivalent of specialisation? With all the same soundness issues?

Of course there are limited workarounds like Castaway — Rust library // Lib.rs that might help for your use case.

1 Like

Not completely. E.g. it isn't possible to introduce differences in return types, associated types or associated constants based on trait implementation using this magic method. A fair bit of the soundness issues with specialisation comes from the ability to let type checking disagree with codegen on which types are involved.

There's still the problem, that - because codegen works on erased lifetimes - the result of T.implements_trait<…>() cannot be defined if a trait is only implemented for a subset of lifetimes on a type.

E.g.

trait Marker {}

impl Marker for &'static str {}

fn is_marked<T>(value: T) -> bool {
    T.implements_trait::<Marker>()
}
// is_marked::<&'static str>(…) must return true
// is_marked::<&'a str>(…) where 'a != 'static must return false

fn main() {
    let foo: &'static str = "foo";
    println!("static str: {}", is_marked(foo)); // should print true

    let bar_buffer = "bar".to_string();
    let bar: &str = &*bar_buffer;
    println!("non-static str: {}", is_marked(bar)); // should print false
}

but codegen would need to only create one instantiation of is_marked for any reference to str:

fn is_marked<&str>(value: &str) -> bool {
    // true of false??
}

The only viable solutions I can think of are

  1. restrict T in implements_trait to 'static similar to the Any trait. That circumvents the problem with lifetime-conditional trait implementations but severely restricts where the check could be used.

  2. call it implements_trait_for_all_lifetimes (bikeshedding/syntax discussion needed) and define it to only return true if not only the concrete type behind T but its whole type family (e.g. in the example for<'a> &'a str implements the trait. That way there would only be false negatives (&'static str does implement Marker but not all &str do → returns false) but no false positives.

1 Like

This looks similar to this feature: Tracking Issue for downcast_trait · Issue #144361 · rust-lang/rust · GitHub

2 Likes

Yeah, it's very similar. That feature is basically the T: 'static version of this idea, except that you also need a value of that type to perform the call to downcast_trait()

Note that you can emulate the value-less version by proxying through another trait (e.g. you can impl ProxyTrait for PhantomData<T> where T: Trait and then you can try downcasting an instance of PhantomData<T>).

The other direction is not possible to emulate: even if you know that T::implements_trait::<dyn Trait>() is true you can't call Traits methods on T!

1 Like