macro_rules! impls {
($type:ty: $($trait_expr:tt)+) => { ... };
}Expand description
Returns true if a type implements a logical trait expression.
§Examples
This macro works in every type context. See below for use cases.
§Constant Evaluation
Because types are compile-time constructs, the result of this macro can be
used as a const value:
const IMPLS: bool = impls!(u8: From<u32>);Using static_assertions, we can fail to compile if the trait expression
evaluates to false:
const_assert!(impls!(*const u8: Send | Sync));§Precedence and Nesting
Trait operations abide by Rust’s expression precedence. To define a custom order of operations (e.g. left-to-right), simply nest the expressions with parentheses.
let pre = impls!(u64: From<u8> | From<u16> ^ From<u32> & From<u64>);
let ltr = impls!(u64: ((From<u8> | From<u16>) ^ From<u32>) & From<u64>);
assert_eq!(pre, true | true ^ true & true);
assert_ne!(pre, ltr);§Mutual Exclusion
Because exclusive-or (^) is a trait operation, we can check that a type
implements one of two traits, but not both:
struct T;
trait Foo {}
trait Bar {}
impl Foo for T {}
assert!(impls!(T: Foo ^ Bar));§Reference Types
Something that’s surprising to many Rust users is that &mut T does not
implement Copy nor Clone:
assert!(impls!(&mut u32: !Copy & !Clone));Surely you’re thinking now that this macro must be broken, because you’ve
been able to reuse &mut T throughout your lifetime with Rust. This works
because, in certain contexts, the compiler silently adds “re-borrows”
(&mut *ref) with a shorter lifetime and shadows the original. In reality,
&mut T is a move-only type.
§Unsized Types
There’s a variety of types in Rust that don’t implement Sized:
// Slices store their size with their pointer.
assert!(impls!(str: !Sized));
assert!(impls!([u8]: !Sized));
// Trait objects store their size in a vtable.
trait Foo {}
assert!(impls!(dyn Foo: !Sized));
// Wrappers around unsized types are also unsized themselves.
struct Bar([u8]);
assert!(impls!(Bar: !Sized));§Generic Types
When called from a generic function, the returned value is based on the constraints of the generic type:
use std::cell::Cell;
struct Value<T> {
// ...
}
impl<T: Send> Value<T> {
fn do_stuff() {
assert!(impls!(Cell<T>: Send));
// ...
}
}Keep in mind that this can result in false negatives:
const fn is_copy<T>() -> bool {
impls!(T: Copy)
}
assert_ne!(is_copy::<u32>(), impls!(u32: Copy));§Lifetimes
Traits with lifetimes are also supported:
trait Ref<'a> {}
impl<'a, T: ?Sized> Ref<'a> for &'a T {}
impl<'a, T: ?Sized> Ref<'a> for &'a mut T {}
assert!(impls!(&'static str: Ref<'static>));
assert!(impls!(&'static mut [u8]: Ref<'static>));
assert!(impls!(String: !Ref<'static>));§Trait-Dependent Type Sizes
This macro enables something really cool (read cursed) that couldn’t be done before: making a type’s size dependent on what traits it implements! Note that this probably is a bad idea and shouldn’t be used in production.
Here Foo becomes 32 bytes for no other reason than it implementing
Clone:
const SIZE: usize = 32 * (impls!(Foo: Clone) as usize);
#[derive(Clone)]
struct Foo([u8; SIZE]);
assert_eq!(std::mem::size_of::<Foo>(), 32);The bool returned from impls! gets casted to a usize, becoming 1
or 0 depending on if it’s true or false respectively. If true, this
becomes 32 × 1, which is 32. This then becomes the length of the byte array
in Foo.