检查特征是否实现

Check if a trait is implemented or not

如何检查结构是否实现了某些特征并计算布尔结果

例如:

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

你可以这样做,但不是在一般情况下

由于特性中固有方法优于方法,因此您可以编写宏来利用这一事实来确定特定类型是否实现了特定特性。

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

但是,这在通用上下文中不起作用,因为方法选择是在单态化之前完成的。所以函数

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

将始终 return 错误。专业化可能会做得更好,但我不会走那条路,特别是如果你是生锈的新手,因为专业化目前还不完善。

我知道三种实现方法。 None 完美。

最好的方法是使用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.

但是,此解决方案存在多个问题:

  • 是nightly-only,专业化没有明确的稳定化途径。
  • Specialization is unsound。稳健性问题不适用于这个具体案例,但是小心点是有好处的,而且...
  • 当前的专业化不精确。这与它不合理的原因相同:当与生命周期一起使用时,编译器可能不会选择正确的 impl。例如:
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.

输出:

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

下一个解决方案是使用 deref specialization 或根据 @Aiden4 所建议的在特征方法之前选择固有方法的事实进行专门化,如果可能的话。不幸的是,如前所述,这不适用于泛型。

最后一种方法最有趣:它利用了标准库中现有的专业化。例如,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.

它可以调整到std中的许多专业化,例如this one

此解决方案使用专业化 under-the-hood,因此 specialization's caveat #3 still applies。然而,它使用现有的专业化,因此可以在稳定版上使用——此外,它因此也是完全可靠的。然而,它还有另一个警告:我不确定 Rust 的稳定性保证是否适用于这种情况,因此它可能会在未来崩溃。除此之外,这个解决方案不是 const - 尽管如果相关功能将来会 const 化,它可能会变成。