问题
While trying to understand the Any
trait better, I saw that it has an impl block for the trait itself. I don't understand the purpose of this construct, or even if it has a specific name.
I made a little experiment with both a "normal" trait method and a method defined in the impl
block:
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.
From this limited experiment, it seems like methods defined in the impl
block are more restrictive than methods defined in the trait
block. It's likely that there's something extra that doing it this way unlocks, but I just don't know what it is yet! ^_^
The sections from The Rust Programming Language on traits and trait objects don't make any mention of this. Searching the Rust source itself, it seems like only Any
and Error use this particular feature. I've not seen this used in the handful of crates where I have looked at the source code.
回答1:
When you define a trait named Foo
that can be made into an object, Rust also defines a trait object type named dyn Foo
. In older versions of Rust, this type was only called Foo
(see What does "dyn" mean in a type?). For backwards compatibility with these older versions, Foo
still works to name the trait object type, although dyn
syntax should be used for new code.
Trait objects have a lifetime parameter that designates the shortest of the implementor's lifetime parameters. To specify that lifetime, you write the type as dyn Foo + 'a
.
When you write impl dyn Foo {
(or just impl Foo {
using the old syntax), you are not specifying that lifetime parameter, and it defaults to 'static
. This note from the compiler on the y.foo_in_impl();
statement hints at that:
note: borrowed value must be valid for the static lifetime...
All we have to do to make this more permissive is to write a generic impl
over any lifetime:
impl<'a> dyn Foo + 'a {
fn foo_in_impl(&self) { println!("in impl") }
}
Now, notice that the self
argument on foo_in_impl
is a borrowed pointer, which has a lifetime parameter of its own. The type of self
, in its full form, looks like &'b (dyn Foo + 'a)
(the parentheses are required due to operator precedence). A Box<u8>
owns its u8
– it doesn't borrow anything –, so you can create a &(dyn Foo + 'static)
out of it. On the other hand, &42u8
creates a &'b (dyn Foo + 'a)
where 'a
is not 'static
, because 42u8
is put in a hidden variable on the stack, and the trait object borrows this variable. (That doesn't really make sense, though; u8
doesn't borrow anything, so its Foo
implementation should always be compatible with dyn Foo + 'static
... the fact that 42u8
is borrowed from the stack should affect 'b
, not 'a
.)
Another thing to note is that trait methods are polymorphic, even when they have a default implementation and they're not overridden, while inherent methods on a trait objects are monomorphic (there's only one function, no matter what's behind the trait). For example:
use std::any::TypeId;
trait Foo {
fn foo_in_trait(&self)
where
Self: 'static,
{
println!("{:?}", TypeId::of::<Self>());
}
}
impl dyn Foo {
fn foo_in_impl(&self) {
println!("{:?}", TypeId::of::<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();
}
Sample output:
TypeId { t: 10115067289853930363 }
TypeId { t: 1357119791063736673 }
TypeId { t: 14525050876321463235 }
TypeId { t: 1357119791063736673 }
In the trait method, we get the type id of the underlying type (here, u8
or u16
), so we can conclude that the type of &self
will vary from one implementer to the other (it'll be &u8
for the u8
implementer and &u16
for the u16
implementer – not a trait object). However, in the inherent method, we get the type id of dyn Foo
(+ 'static
), so we can conclude that the type of &self
is always &dyn Foo
(a trait object).
回答2:
I suspect that the reason is very simple: may be overridden or not?
A method implemented in a trait
block can be overridden by implementors of the trait
, it just provides a default.
On the other hand, a method implemented in an impl
block cannot be overridden.
If this reasoning is right, then the error you get for See Francis Gagné's more complete answer on the interaction with lifetimes.y.foo_in_impl()
is just a lack of polish: it should have worked.
来源:https://stackoverflow.com/questions/34438755/why-would-i-implement-methods-on-a-trait-instead-of-as-part-of-the-trait