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>());
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>());
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 constified in the future.
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.