为啥我要在特征上实现方法而不是作为特征的一部分?

Posted

技术标签:

【中文标题】为啥我要在特征上实现方法而不是作为特征的一部分?【英文标题】:Why would I implement methods on a trait instead of as part of the trait?为什么我要在特征上实现方法而不是作为特征的一部分? 【发布时间】:2016-03-30 01:40:58 【问题描述】:

在尝试更好地理解 Any 特征时,我看到了 has an impl block for the trait itself。我不明白这个构造的目的,或者即使它有一个特定的名称。

我用“正常”特征方法和impl 块中定义的方法做了一个小实验:

trait Foo 
    fn foo_in_trait(&self) 
        println!("in foo")
    


impl dyn Foo 
    fn foo_in_impl(&self) 
        println!("in impl")
    


impl Foo for u8 

fn main() 
    let x = Box::new(42u8) as Box<dyn Foo>;
    x.foo_in_trait();
    x.foo_in_impl();

    let y = &42u8 as &dyn Foo;
    y.foo_in_trait();
    y.foo_in_impl(); // May cause an error, see below

编者注

在 Rust 1.15.0 及之前的版本中,该行 y.foo_in_impl() 导致错误:

error: borrowed value does not live long enough
  --> src/main.rs:20:14
   |
20 |     let y = &42u8 as &Foo;
   |              ^^^^ does not live long enough
...
23 | 
   | - temporary value only lives until here
   |
   = note: borrowed value must be valid for the static lifetime...

此错误在后续版本中不再存在,但 答案中解释的概念仍然有效。

从这个有限的实验来看,impl 块中定义的方法似乎比trait 块中定义的方法更严格。这样做可能会解锁一些额外的东西,但我只是还不知道它是什么! ^_^

traits 和 trait objects 上的 Rust 编程语言 部分没有提及这一点。搜索 Rust 源代码本身,似乎只有 AnyError 使用此特定功能。在我查看源代码的少数 crate 中,我没有看到它使用过。

【问题讨论】:

非常有趣的问题!特征块中的SelfFooimpl 块中的SelfFoo + 'static ... 【参考方案1】:

当你定义一个名为 Foo 的 trait 可以被制作成一个对象时,Rust 还定义了一个名为 dyn Foo 的 trait 对象类型。在旧版本的 Rust 中,这种类型仅称为 Foo(请参阅 What does "dyn" mean in a type?)。为了向后兼容这些旧版本,Foo 仍然可以命名 trait 对象类型,尽管 dyn 语法应该用于新代码。

特征对象有一个生命周期参数,它指定了实现者生命周期参数中最短的一个。要指定该生命周期,请将类型写为 dyn Foo + 'a

当你写impl dyn Foo (或者只是使用旧语法impl Foo )时,你没有指定生命周期参数,它默认为'static。编译器对y.foo_in_impl(); 语句的注释暗示了这一点:

注意:借用的值必须在静态生命周期内有效...

为了让这更宽容,我们要做的就是在任何生命周期内编写一个通用的impl

impl<'a> dyn Foo + 'a 
    fn foo_in_impl(&self)  println!("in impl") 

现在,请注意foo_in_impl 上的self 参数是一个借用的指针,它有自己的生命周期参数。 self 的类型在其完整形式中类似于 &amp;'b (dyn Foo + 'a)(由于运算符优先级,括号是必需的)。一个Box&lt;u8&gt; 拥有它的u8——它不借用任何东西——所以你可以用它创建一个&amp;(dyn Foo + 'static)。另一方面,&amp;42u8 创建了一个&amp;'b (dyn Foo + 'a),其中'a 不是'static,因为42u8 被放入堆栈上的一个隐藏变量中,并且特征对象借用了这个变量。 (不过,这并没有什么意义;u8 没有借用任何东西,所以它的Foo 实现应该始终与dyn Foo + 'static 兼容...42u8 从堆栈中借用的事实应该会影响'b,不是'a。)

要注意的另一件事是 trait 方法是多态的,即使它们具有默认实现并且它们没有被覆盖,而 trait 对象上的固有方法是单态的(只有一个函数,无论 trait 背后是什么) .例如:

use std::any::type_name;

trait Foo 
    fn foo_in_trait(&self)
    where
        Self: 'static,
    
        println!("", type_name::<Self>());
    


impl dyn Foo 
    fn foo_in_impl(&self) 
        println!("", type_name::<Self>());
    


impl Foo for u8 
impl Foo for u16 

fn main() 
    let x = Box::new(42u8) as Box<dyn Foo>;
    x.foo_in_trait();
    x.foo_in_impl();

    let x = Box::new(42u16) as Box<Foo>;
    x.foo_in_trait();
    x.foo_in_impl();

样本输出:

u8
dyn playground::Foo
u16
dyn playground::Foo

在 trait 方法中,我们得到底层类型的类型名称(这里是 u8u16),因此我们可以得出结论,&amp;self 的类型会因一个实现者而异(它' 将是 &amp;u8 用于 u8 实现者和 &amp;u16 用于 u16 实现者 - 不是特征对象)。但是,在固有方法中,我们得到了dyn Foo+ 'static)的类型名称,因此我们可以得出&amp;self的类型始终是&amp;dyn Foo(一个trait对象)。

【讨论】:

【参考方案2】:

怀疑原因很简单:可能被覆盖还是不被覆盖?

trait 块中实现的方法可以被trait 的实现者覆盖,它只是提供一个默认值。

另一方面,在 impl 块中实现的方法不能被覆盖。

如果这个推理是正确的,那么您为 y.foo_in_impl() 得到的错误只是缺乏修饰:它应该有效。 请参阅 Francis Gagné 关于与交互的更完整的答案生命周期。

【讨论】:

您的回答在某种意义上更好。它预先回答了疑问的核心部分。复杂的细节可以遵循,但主要思想是预先提出的;另一个答案是相反的。

以上是关于为啥我要在特征上实现方法而不是作为特征的一部分?的主要内容,如果未能解决你的问题,请参考以下文章

为啥作为参数传递的特征对象的生命周期需要更高等级的特征边界,而结构不需要?

特征和传递特征作为模板参数

在磁盘上实现的 FIFO 队列(或堆栈),而不是 ram(最好在 C++ 中)[关闭]

c++大特征分解速度

在数字分类数据上实现朴素贝叶斯高斯分类器

为啥我可以在 MPMoviePlayer 中播放一部电影而不能播放另一部电影?