Reputation: 291
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
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
}
However, this solution has multiple problems:
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);
}
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()
}
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 const
ified in the future.
Upvotes: 7
Reputation: 2654
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
}
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