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

Why would I implement methods on a trait instead of as part of the trait?

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

我用 "normal" 特征方法和 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
}

Editor's note

In versions of Rust up to and including Rust 1.15.0, the line y.foo_in_impl() causes the error:

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...

This error is no longer present in subsequent versions, but the concepts explained in the answers are still valid.

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

The Rust Programming Language traits and trait objects don't make any mention of this. Searching the Rust source itself, it seems like only Any and Error 中的部分使用了此特定功能。在我查看源代码的少数板条箱中,我没有看到它被使用过。

怀疑原因很简单:有没有被覆盖?

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

另一方面,无法覆盖 impl 块中实现的方法。

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

当你定义一个名为 Foo 的 trait 可以成为一个对象时,Rust 也定义了一个名为 dyn Foo 的 trait 对象类型。在旧版本的 Rust 中,这种类型只被称为 Foo(参见 )。为了与这些旧版本向后兼容,Foo 仍然可以命名特征对象类型,尽管 dyn 语法应该用于新代码。

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

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

note: borrowed value must be valid for the static lifetime...

我们要做的就是在任何生命周期内编写一个通用的impl

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

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

另一件需要注意的事情是特征方法是多态的,即使它们有一个默认实现并且它们没有被覆盖,而特征对象的固有方法是单态的(只有一个函数,不管背后是什么特征)。例如:

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),所以我们可以得出结论&self的类型会有所不同从一个实现者到另一个实现者(u8 实现者是 &u8u16 实现者是 &u16——不是特征对象)。但是,在固有方法中,我们得到了dyn Foo的类型名(+ 'static),所以我们可以得出结论,&self的类型总是&dyn Foo(一个trait对象).