为啥我要在特征上实现方法而不是作为特征的一部分?
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 源代码本身,似乎只有 Any
和 Error
使用此特定功能。在我查看源代码的少数 crate 中,我没有看到它使用过。
【问题讨论】:
非常有趣的问题!特征块中的Self
是Foo
和impl
块中的Self
是Foo + '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
的类型在其完整形式中类似于 &'b (dyn Foo + 'a)
(由于运算符优先级,括号是必需的)。一个Box<u8>
拥有它的u8
——它不借用任何东西——所以你可以用它创建一个&(dyn Foo + 'static)
。另一方面,&42u8
创建了一个&'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 方法中,我们得到底层类型的类型名称(这里是 u8
或 u16
),因此我们可以得出结论,&self
的类型会因一个实现者而异(它' 将是 &u8
用于 u8
实现者和 &u16
用于 u16
实现者 - 不是特征对象)。但是,在固有方法中,我们得到了dyn Foo
(+ 'static
)的类型名称,因此我们可以得出&self
的类型始终是&dyn Foo
(一个trait对象)。
【讨论】:
【参考方案2】:我怀疑原因很简单:可能被覆盖还是不被覆盖?
trait
块中实现的方法可以被trait
的实现者覆盖,它只是提供一个默认值。
另一方面,在 impl
块中实现的方法不能被覆盖。
如果这个推理是正确的,那么您为 请参阅 Francis Gagné 关于与交互的更完整的答案生命周期。y.foo_in_impl()
得到的错误只是缺乏修饰:它应该有效。
【讨论】:
您的回答在某种意义上更好。它预先回答了疑问的核心部分。复杂的细节可以遵循,但主要思想是预先提出的;另一个答案是相反的。以上是关于为啥我要在特征上实现方法而不是作为特征的一部分?的主要内容,如果未能解决你的问题,请参考以下文章
为啥作为参数传递的特征对象的生命周期需要更高等级的特征边界,而结构不需要?