Stone
Stone

Reputation: 291

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>());

Upvotes: 5

Views: 5063

Answers (2)

Chayim Friedman
Chayim Friedman

Reputation: 70840

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.

Upvotes: 7

Aiden4
Aiden4

Reputation: 2654

You can do this, but not in a generic context

Because inherent methods are preferred over methods in traits, you can write a macro taking advantage of the fact to determine if a specific type implements a specific trait.

macro_rules! is_trait{
    ($name:ty, $trait_name:path)=>{{
        trait __InnerMarkerTrait{
            fn __is_trait_inner_method()->bool{
                false
            }
        }
        struct __TraitTest<T>(T);
        impl<T:$trait_name> __TraitTest<T> {
            fn __is_trait_inner_method()->bool{
                true
            }
        }
        impl<T> __InnerMarkerTrait for __TraitTest<T>{}
        __TraitTest::<$name>::__is_trait_inner_method()
    }}
}
fn main() {
    println!("{}", is_trait!(i32, Copy)); //true
    println!("{}", is_trait!(String, Copy)); //false
}

Playground

However, this won't work in a generic context, because the method selection is done before monomorphization. So the function

fn is_copy<T>() -> bool{
    is_trait!(T, Copy)
}

will always return false. It may be possible to do better with specialization, but I would not go that route, especially if you are new to rust, because specialization is currently unsound.

Upvotes: 4

Related Questions