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
.