Macro impls

Source
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.