如何在编译时强制实现类型继承一个特征?

8
我希望写一个类似这样的宏:
```html

我想写一个这样的宏:

```
macro_rules! a {
    ( $n:ident, $t:ty ) => {
         struct $n {
             x: $t
         }
    }
}

但是$t应该实现AddSubMul特质。我如何在编译时检查它?

1个回答

10

首先,在不使用宏的情况下解决问题。一种解决方案是创建未记录的私有函数,如果条件不满足,则会导致编译失败:

struct MyType {
    age: i32,
    name: String,
}

const _: () = {
    fn assert_send<T: Send>() {}
    fn assert_sync<T: Sync>() {}

    // RFC 2056
    fn assert_all() {
        assert_send::<MyType>();
        assert_sync::<MyType>();
    }
};

然后,修改简单的解决方案以使用宏:
macro_rules! example {
    ($name:ident, $field:ty) => {
        struct $name {
            x: $field,
        }

        const _: () = {
            fn assert_add<T: std::ops::Add<$field, Output = $field>>() {}
            fn assert_mul<T: std::ops::Mul<$field, Output = $field>>() {}

            // RFC 2056
            fn assert_all() {
                assert_add::<$field>();
                assert_mul::<$field>();
            }
        };
    };
}

example!(Moo, u8);
example!(Woof, bool);

在这两种情况下,我们创建一个虚拟的const值来限定函数及其调用的作用域,以避免名称冲突。
接着,我会相信优化器会在编译时删除代码,所以我不会期望任何额外的臃肿。
非常感谢Chris Morgan提供了一个更好的版本,支持非对象安全的traits。
值得强调的是RFC 2056 ,它将允许在where从句中使用“琐碎”的约束。一旦实施,将接受这样的从句:
impl Foo for Bar
where 
    i32: Iterator,
{}

在Rust的历史中,这种精确行为已经多次改变,并且RFC 2056将其固定下来。为了保持我们在这种情况下想要的行为,我们需要从另一个没有约束条件的函数中调用断言函数(因此必须始终为真)。


3
我个人会选择 fn _assert_add() where Self: Add<Self, Output = Self> { } fn _verify_assertions() { Self::_assert_add() }。在我看来,这样可以得到更简洁的错误信息,并且更加通用,因为它不需要 trait 是对象安全的。 - Chris Morgan
@ChrisMorgan 不是正在创建的类型而是字段的类型吗? - Shepmaster
2
哦,是的,我没有完全阅读它。但这没问题——where $t: Add<$t, Output = $t>同样适用。where从句可以包含任何类型的内容,不仅限于与Self或泛型相关的内容。 - Chris Morgan
1
@ChrisMorgan where 子句可以包含任何类型的内容* — 惊呆了。此外,以下划线开头的函数不会触发未使用函数的警告。这很有道理,但仍然令人惊讶! - Shepmaster
@ChrisMorgan 是的,我正在尝试决定哪个提供更好的错误消息,并认为具有方法名称的冗余并不可怕。如果不需要调用函数,我肯定会将它们合并。 - Shepmaster
显示剩余2条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接