【问题标题】:Use trait blanket implementation to restrict type trait bounds in Rust?使用特征毯实现来限制 Rust 中的类型特征边界?
【发布时间】:2021-11-27 02:34:35
【问题描述】:

在下面的代码中:Rust Playground

  //////////////////////////////////////
 // External code from another crate //
//////////////////////////////////////

trait FooExternal {
    fn foo(&self);
}

fn foo_external(f: impl FooExternal) {
    f.foo();
}

  /////////////////////////////////////
 // My generated library code below //
/////////////////////////////////////

trait FooImpl {
    fn foo_impl(&self);
}

impl<T: FooImpl> FooExternal for T {
    fn foo(&self) {
        println!("foo: boilerplate");
        self.foo_impl();
    }
}

  ////////////////////////
 // My user code below //
////////////////////////

#[derive(Debug, Default)]
struct Foo;

// NB: the compiler will yell if FooImpl is not implemented
// try commenting out the impl below
impl FooImpl for Foo {
    fn foo_impl(&self) {
        println!("foo_impl");
    }
}

fn main() {
    println!("Hello, world!");
    let f = Foo::default();
    foo_external(f);
}

外部代码希望用户提供实现FooExternal 的类型,因此foo_external 可以在该类型的实例上工作。

但是,当为用户定义的结构 Foo 实现 FooExternal 特征时,有一些样板代码(例如,为每个 tonic grpc 处理程序设置 opentelemetry 跟踪跨度上下文)很常见且容易出错且有点复杂要求最终用户输入。

所以我打算生成(想想:proc 宏,但 codegen 不是这里的问题!)FooExternal 的常见样板实现,并希望用户只关注核心应用程序逻辑,而不是担心复杂的以及一遍又一遍地键入相同的样板的无聊苦差事!

因此,我希望用户实现生成的特征FooImpl,而不是让用户为她的类型FooExternal 实现FooImpl,其中在FooExternal::foo 的生成的整体特征实现中,样板代码是发出,并将控制转发给FooImpl::foo_impl

这是用户代码中的好东西:FooImpl 成为用户类型 Foo 的要求(使用 foo_external 时) - 如果用户忘记为 FooImpl 实现 Foo,编译器会拨打foo_external(f)时请对你大喊大叫!

因此,似乎特征毯实现有效地将FooExternal 绑定在FooImpl 上——甚至编译器错误消息(当impl FooImpl for Foo 被注释掉时)说:

   Compiling playground v0.0.1 (/playground)
error[E0277]: the trait bound `Foo: FooImpl` is not satisfied
  --> src/main.rs:49:18
   |
49 |     foo_external(f);
   |                  ^ the trait `FooImpl` is not implemented for `Foo`
   |
note: required because of the requirements on the impl of `FooExternal` for `Foo`
  --> src/main.rs:23:18
   |
23 | impl<T: FooImpl> FooExternal for T {
   |                  ^^^^^^^^^^^     ^
note: required by a bound in `foo_external`
  --> src/main.rs:11:25
   |
11 | fn foo_external(f: impl FooExternal) {
   |                         ^^^^^^^^^^^ required by this bound in `foo_external`

For more information about this error, try `rustc --explain E0277`.
error: could not compile `playground` due to previous error

所以,我想知道是否曾经设计用于在这种情况下使用的特征毯实现(基本上将 FooImplFooExternal 绑定在一起,有点像扩展特征 trait FooExternal: FooImpl,但不完全是!),我可以依靠这种行为来定义我的代码生成逻辑来生成代码示例中的库代码吗?

任何见解将不胜感激!

【问题讨论】:

    标签: rust


    【解决方案1】:

    根据trait coherence rulesimpl&lt;T: FooImpl&gt; FooExternal for T 必须与FooExternal 在同一个 crate 中。所以,如果你不控制 FooExternal 中定义的 crate,你就不能这样做。

    规则的设计使得添加更多 crate 永远不会造成在不存在的 trait 实现之间发生冲突。只能一个一揽子实现impl&lt;T...&gt; FooExternal for T(因为如果有多个,它们可能都适用于同一类型T),并且只允许定义FooExternal的板条箱编写那个实现。

    【讨论】:

    • 这很有意义!我仔细检查了一下,幸运的是,FooExternal 似乎在我自己的箱子里,它只是由 tonic 生成的。所以在这种情况下,我想我的用例确实有效并且有意义?
    • @Dejavu 是的,在这种情况下它应该可以工作,但请注意,拥有一揽子 impl 并不能阻止其他 crate 直接实现 FooExternal 并忽略一揽子 impl。
    猜你喜欢
    • 2020-08-17
    • 1970-01-01
    • 1970-01-01
    • 2022-08-15
    • 2016-06-02
    • 2019-12-08
    • 2021-06-07
    • 1970-01-01
    • 2022-10-05
    相关资源
    最近更新 更多