• Stars
    star
    246
  • Rank 164,726 (Top 4 %)
  • Language
    Rust
  • License
    Apache License 2.0
  • Created almost 5 years ago
  • Updated almost 5 years ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

A Rust macro to determine if a type implements a logical trait expression

Determine if a type implements a logical trait expression?, brought to you by @NikolaiVazquez!

This library defines impls!, a macro? that returns a bool indicating whether a type implements a boolean-like expression over a set of traits?.

assert!(impls!(String: Clone & !Copy & Send & Sync));

See "Examples" for detailed use cases and, if you're brave, see "Trait-Dependent Type Sizes" for some cursed code.

Index

Reasoning

As a library author, it's important to ensure that your API remains stable. Trait implementations are part of API stability. For example: if you accidentally introduce an inner type that makes your publicly-exposed type no longer be Send or Sync, you've now broken your API without noticing it! The most common case of this happening is adding a raw pointer (i.e. *const T, *mut T) as a type field.

By checking situations like this with impls!, either at compile-time or in a unit test, you can ensure that no API-breaking changes are made without noticing until it's too late.

Usage

This crate is available on crates.io and can be used by adding the following to your project's Cargo.toml:

[dependencies]
impls = "1"

and this to your crate root (main.rs or lib.rs):

#[macro_use]
extern crate impls;

When using Rust 2018 edition, the following import can help if having #[macro_use] is undesirable.

use impls::impls;

Vocabulary

This documentation uses jargon that may be new to inexperienced Rust users. This section exists to make these terms easier to understand. Feel free to skip this section if these are already familiar to you.

Macro

In Rust, macros are functions over the abstract syntax tree (AST). They map input tokens to output tokens by performing some operation over them through a set of rules. Because of this, only their outputs are ever type-checked.

If you wish to learn about implementing macros, I recommend:

To use this crate, you do not need to know how macros are defined.

Trait

In Rust, traits are a way of defining a generalized property. They should be thought of expressing what a type is capable of doing. For example: if a type implements Into for some type T, then we know it can be converted into T by just calling the .into() method on it.

If you wish to learn about traits in detail, I recommend:

Logical Trait Expression

In this crate, traits should be thought of as bools where the condition is whether the given type implements the trait or not.

An expression can be formed from these trait operations:

  • And (&): also known as logical conjunction, this returns true if both operands are true. This is usually defined in Rust via the BitAnd trait.

  • Or (|): also known as logical disjunction, this returns true if either of two operands is true. This is usually defined in Rust via the BitOr trait.

  • Exclusive-or (^): also known as exclusive disjunction, this returns true if only one of two operands is true. This is usually defined in Rust via the BitXor trait.

  • Not (!): a negation that returns false if the operand is true, or true if the operand is false. This is usually defined in Rust via the Not trait.

See "Precedence and Nesting" for information about the order in which these operations are performed.

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.

How It Works

This abuses inherent impl priority to determine that a trait is implemented:

// Fallback trait for to all types to default to `false`.
trait NotCopy {
    const IS_COPY: bool = false;
}
impl<T> NotCopy for T {}

// Concrete wrapper type where `IS_COPY` becomes `true` if `T: Copy`.
struct IsCopy<T>(std::marker::PhantomData<T>);

impl<T: Copy> IsCopy<T> {
    // Because this is implemented directly on `IsCopy`, it has priority over
    // the `NotCopy` trait impl.
    //
    // Note: this is a *totally different* associated constant from that in
    // `NotCopy`. This does not specialize the `NotCopy` trait impl on `IsCopy`.
    const IS_COPY: bool = true;
}

assert!(IsCopy::<u32>::IS_COPY);
assert!(!IsCopy::<Vec<u32>>::IS_COPY);

Authors

  • Nikolai Vazquez (GitHub: @nvzqz, Twitter: @NikolaiVazquez)

    Implemented the impls! macro with support for all logical operators and without the limitations of the initial does_impl! macro by Nadrieril.

  • Nadrieril Feneanar (GitHub: @Nadrieril)

    Implemented the initial does_impl! macro in nvzqz/static-assertions-rs#28 upon which this crate was originally based.

License

This project is released under either:

at your choosing.

More Repositories

1

FileKit

Simple and expressive file management in Swift
Swift
2,289
star
2

RandomKit

Random data generation in Swift
Swift
1,473
star
3

divan

Fast and simple benchmarking for Rust projects
Rust
895
star
4

static-assertions

Ensure correct assumptions about constants, types, and more in Rust
Rust
566
star
5

Sage

A cross-platform chess library for Swift
Swift
376
star
6

Menubar-Colors

A macOS app for convenient access to the system color panel
Swift
175
star
7

fruity

Rusty bindings for Apple libraries
Rust
169
star
8

swift-bindgen

Bridging the gap between Swift and Rust
Rust
149
star
9

Unreachable

Unreachable code path optimization hint for Swift
Swift
103
star
10

Threadly

Type-safe thread-local storage in Swift
Swift
74
star
11

condtype

Choose Rust types at compile-time via constants
Rust
59
star
12

embed-plist-rs

Embed property list files like Info.plist directly in your Rust executable binary
Rust
39
star
13

Roman

Seamless Roman numeral conversion in Swift
Swift
36
star
14

rosy

Rust and Ruby, sitting in a tree 🌹
Rust
23
star
15

byte-set-rs

Efficient sets of bytes for Rust
Rust
19
star
16

cargo-emit

Talk to Cargo easily at build time
Rust
16
star
17

Clockmoji

Simple and intuitive time formatting using clock emoji.
Swift
13
star
18

XO

A cross-platform tic-tac-toe library for Swift
Swift
13
star
19

Lazy

Save the hard work for later, lazily evaluate values anywhere
Swift
12
star
20

Weak

Weak and Unowned as native Swift types
Swift
10
star
21

typebool

Type-level booleans in Rust for compile-time hackery
Rust
8
star
22

c-utf8-rs

UTF-8 encoded C strings for Rust
Rust
6
star
23

malloced

A malloc-ed box pointer type for Rust
Rust
5
star
24

dotfiles

Personal dotfiles
Shell
5
star
25

aloxide

Compile Rubies on Linux, macOS, and Windows
Rust
5
star
26

mods

Simpler Rust module declaration
Rust
5
star
27

bit-collection-rs

Iterate over bits in Rust
Rust
5
star
28

unsafe-unwrap-rs

Unsafely unwrap Result and Option types without checking in Rust
Rust
5
star
29

uncon-rs

Unchecked and unsafe type conversions in Rust
Rust
4
star
30

head-rs

Common Rust types with inline headers, such as HeaderVec for Vec
Rust
4
star
31

bad-rs

Unlicensed bad ideas
Rust
4
star
32

rust-workshop

A workshop on the Rust programming language
3
star
33

OneOrMore

A Swift collection of one or more elements
Swift
3
star
34

fmty

Rust library of composable `core::fmt` utilities
Rust
3
star
35

mem-cmp-rs

Safe memory comparison between types in Rust
Rust
2
star
36

ShiftOperations

A µframework for a needed ShiftOperations protocol that does not appear in the Swift standard library
Swift
1
star
37

rust-selector-example

My attempt at creating Objective-C selectors in Rust at compile time
Shell
1
star
38

asygnal

Handle signals (e.g. ctrl-c) efficiently and asynchronously in Rust
Rust
1
star
39

chance-rs

Random number generation in Rust
Rust
1
star