#traits #test-macro #impl #macro #test

no-std does_impl

[WIP] 判断类型是否实现了逻辑特征表达式

1个不稳定版本

使用旧的Rust 2015

0.0.0 2019年12月30日

#36 in #test-macro

MIT/Apache

6KB


判断类型是否实现了逻辑特征表达式?,由 @NikolaiVazquez 提供!

此库定义了 impls! 宏,它返回一个 bool,表示类型是否在一系列特征上实现了类似布尔的表达式?

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

请参阅 "示例" 了解详细用法,如果您敢尝试,还可以查看 "特征依赖类型大小" 中的某些诅咒代码。

索引

推理

作为库作者,确保API保持稳定非常重要。特征实现是API稳定性的一个部分。例如:如果您不小心引入了一个内部类型,使您公开暴露的类型不再为 SendSync,那么您现在已经破坏了API,而没有意识到这一点!这种情况最常见的情况是添加一个 原始指针(即 *const T*mut T)作为类型字段。

通过使用 impls! 检查这种情况,无论是在 编译时 还是在单元测试中,您都可以确保不会在不知不觉中做出破坏API的更改,直到为时已晚。

用法

此crate可在 crates.io 上找到,可以通过将以下内容添加到项目 Cargo.toml

[dependencies]
impls = "1"

和此内容添加到crate根目录(main.rslib.rs

#[macro_use]
extern crate impls;

当使用Rust 2018 版本时,如果您不希望使用#[macro_use],下面的导入语句可能会有帮助。

use impls::impls;

词汇表

本文档使用了可能对不熟悉Rust的初学者来说较为陌生的术语。本节旨在使这些术语更容易理解。如果您对这些术语已经很熟悉,可以自由跳过这一节。

在Rust中,宏是对抽象语法树(AST)的操作。它们通过一系列规则对这些输入标记执行某些操作,将其映射到输出标记。因此,只有它们的输出才被进行类型检查。

如果您想了解宏的实现,我推荐以下资源:

要使用此crate,您不需要了解宏是如何定义的。

特征

在Rust中,特质是一种定义通用属性的方式。它们应该被理解为表达类型能够做什么。例如:如果一个类型为某种类型T实现了Into特质,那么我们知道可以通过调用该对象的.into()方法来将其转换为T

如果您想详细了解特质,我推荐以下资源:

逻辑特征表达式

在这个crate中,特质应该被理解为bool类型,条件是给定的类型是否实现了该特质。

可以从这些特质操作中形成一个表达式

  • 和(&):也称为逻辑与,当两个操作数都为true时返回true。这通常通过Rust中的BitAnd特质定义。

  • 或(|):也称为逻辑或,当两个操作数中至少有一个为true时返回true。这通常通过Rust中的BitOr特质定义。

  • 异或(^):也称为逻辑异或,当两个操作数中只有一个为true时返回true。这通常通过Rust中的BitXor特质定义。

  • 非(!):如果操作数是true,则返回false;如果操作数是false,则返回true。这通常通过Rust的Not特质定义。

有关这些运算符执行顺序的信息,请参阅"优先级和嵌套"。

示例

此宏在所有类型上下文中都有效。下面是使用案例。

常量评估

因为类型是编译时结构,此宏的结果可以用作const值。

const IMPLS: bool = impls!(u8: From<u32>);

使用static_assertions,如果特质表达式评估为false,则无法编译。

const_assert!(impls!(*const u8: Send | Sync));

优先级和嵌套

特质运算符遵循Rust的表达式优先级。要定义运算符的特定顺序(例如左到右),只需使用括号嵌套表达式。

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);

互斥

因为异或(^)是特质运算符,所以我们可以检查类型是否实现两个特质之一,但不能同时实现。

struct T;

trait Foo {}
trait Bar {}

impl Foo for T {}

assert!(impls!(T: Foo ^ Bar));

引用类型

对于许多Rust用户来说,令人惊讶的是,&mut T 实现Copy 也不 实现Clone

assert!(impls!(&mut u32: !Copy & !Clone));

你可能会想,这个宏肯定是有问题的,因为你在整个生命周期中都能重复使用&mut T。这是因为,在特定上下文中,编译器会默默地添加具有较短生命周期的"re-borrows"(&mut *ref),并覆盖原始引用。实际上,&mut T是一个移动类型。

未大小类型

Rust中有许多类型没有实现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));

泛型类型

当从泛型函数调用时,返回值基于泛型类型的约束。

use std::cell::Cell;

struct Value<T> {
    // ...
}

impl<T: Send> Value<T> {
    fn do_stuff() {
        assert!(impls!(Cell<T>: Send));
        // ...
    }
}

请注意,这可能会导致错误的否定。

const fn is_copy<T>() -> bool {
    impls!(T: Copy)
}

assert_ne!(is_copy::<u32>(), impls!(u32: Copy));

生命周期

支持具有生命周期的特质。

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>));

特征依赖类型大小

此宏实现了以前无法做到的真正酷(但可能是诅咒)的事情:使类型的大小取决于它实现的特质!请注意,这可能是一个糟糕的想法,不应该在生产环境中使用。

在这里,Foo的大小变成32字节,原因仅仅是它实现了Clone

const SIZE: usize = 32 * (impls!(Foo: Clone) as usize);

#[derive(Clone)]
struct Foo([u8; SIZE]);

assert_eq!(std::mem::size_of::<Foo>(), 32);

从宏 impls! 返回的 bool 类型会被转换成 usize 类型,其值为 1 或 0,分别对应于 truefalse。如果为 true,则该值变为 32 × 1,即 32。这将成为结构体 Foo 中字节数组的长度。

工作原理

这利用了内建实现(inherent impl)的优先级来确定是否实现了某个特质

// 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);

作者

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

    实现了宏 impls!,支持所有逻辑运算符,并且没有 Nadrieril 的初始宏 does_impl! 的限制。

  • Nadrieril Feneanar(GitHub:@Nadrieril

    nvzqz/static-assertions-rs#28 中实现了初始宏 does_impl!,本仓库最初基于此实现。

许可

该项目可以选择以下任意一种许可证发布:

由您选择。

无运行时依赖