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 const
ified in the future.