Search code examples
rusttraits

Check if a trait is implemented or not


How to check if a struct implement some trait and calculate boolean result.

For example:

struct Foo;
struct Bar;

trait Qux {}
impl Qux for Foo {}
const fn is_qux<T>() -> bool { ... }

assert!(is_qux::<Foo>());
assert_ne!(is_qux::<Bar>());

Solution

  • I know three ways to implement that. None is perfect.

    The best way is to use specialization:

    #![feature(specialization)]
    
    const fn is_qux<T: ?Sized>() -> bool {
        trait IsQuxTest {
            const IS_QUX: bool;
        }
        impl<T: ?Sized> IsQuxTest for T {
            default const IS_QUX: bool = false;
        }
        impl<T: ?Sized + Qux> IsQuxTest for T {
            const IS_QUX: bool = true;
        }
        
        <T as IsQuxTest>::IS_QUX
    }
    

    Playground.

    However, this solution has multiple problems:

    • It is nightly-only, and specialization doesn't have a clear path toward stabilization.
    • Specialization is unsound. The soundness problem does not apply to this specific case, however it is good to be careful, and also...
    • Current specialization is imprecise. This is for the same reason it is unsound: when used with lifetimes, the compiler may not choose the correct impl. For example:
    fn foo<'a>(v: &'a String) {
        struct Foo<'a>(&'a String);
        impl Qux for Foo<'static> {}
        println!("{}", is_qux::<Foo<'a>>());
    }
    
    fn main() {
        let v = String::new();
        static S: String = String::new();
        print!("is_qux::<Foo<'static>>() = "); foo(&S);
        print!("is_qux::<Foo<'non_static>>() = "); foo(&v);
    }
    

    Playground.

    Output:

    is_qux::<Foo<'static>>() = true
    is_qux::<Foo<'non_static>>() = true // ???
    

    The next solution is to use deref specialization or specialize based on the facts that inherent methods are picked before trait methods, if possible, as suggested by @Aiden4. Unfortunately, like said, this does not work with generics.

    The last approach is the most interesting: it exploits existing specializations in the standard library. For example, arrays implement Clone, but if the element type implements Copy, they use bitwise copy to clone themselves (as this is faster). However, Clone is not required to do the same as Copy (although strongly advised to do so) - and this fact makes this behavior observable, and allows us to use the following neat trick (the idea is from here):

    fn is_qux<T: ?Sized>() -> bool {
        use std::cell::Cell;
        use std::marker::PhantomData;
    
        struct IsQuxTest<'a, T: ?Sized> {
            is_qux: &'a Cell<bool>,
            _marker: PhantomData<T>,
        }
        impl<T: ?Sized> Clone for IsQuxTest<'_, T> {
            fn clone(&self) -> Self {
                self.is_qux.set(false);
                IsQuxTest {
                    is_qux: self.is_qux,
                    _marker: PhantomData,
                }
            }
        }
        impl<T: ?Sized + Qux> Copy for IsQuxTest<'_, T> {}
    
        let is_qux = Cell::new(true);
        _ = [IsQuxTest::<T> {
            is_qux: &is_qux,
            _marker: PhantomData,
        }]
        .clone();
    
        is_qux.get()
    }
    

    Playground.

    It can be adjusted to many specializations in std, for example this one.

    This solution uses specialization under-the-hood, and so specialization's caveat #3 still applies. It uses existing specializations, however, and so usable on stable - and furthermore, it is thus also completely sound. However, it has another caveat: I'm not sure Rust's stability guarentees apply in this case, and thus it may break in the future. In addition to that, this solution is not const - though it may become if the relevant functions will be constified in the future.