It isn’t the same. The second trait can be implemented by types T that don’t fulfill T: Sized + 'a (although it seems tricky or maybe even impossible to actually write an implementation for concrete unsized types. But the compiler can do it for the dyn Decode<'a> trait object type.) The bound on the method will then prohibit the method from being called unless T: Sized + 'a holds true. On the implementing end, you’ll get the bound as an assumption, so e.g.
pub trait Decode<'a> {
fn decode(code: u8, stream: &'a [u8]) -> Result<Self, ()> where Self: Sized + 'a;
}
impl<'a, 'b> Decode<'a> for &'b () {
fn decode(code: u8, stream: &'a [u8]) -> Result<Self, ()> where Self: Sized + 'a {
assert_outlives::<'a, 'b>(); // <- you can see that the compiler provides us with the knowledge
// that 'b: 'a is true inside of this method body. (Due to &'b (): 'a.)
todo!()
}
}
fn assert_outlives<'a, 'b: 'a>() {}
error[E0478]: lifetime bound not satisfied
--> src/lib.rs:5:14
|
5 | impl<'a, 'b> Decode<'a> for &'b () {
| ^^^^^^^^^^
|
note: lifetime parameter instantiated with the lifetime `'b` as defined on the impl at 5:10
--> src/lib.rs:5:10
|
5 | impl<'a, 'b> Decode<'a> for &'b () {
| ^^
note: but lifetime parameter must outlive the lifetime `'a` as defined on the impl at 5:6
--> src/lib.rs:5:6
|
5 | impl<'a, 'b> Decode<'a> for &'b () {
| ^^
error: aborting due to previous error
This trait has no Default behavior whatsoever yet, so we won't be able to write .take(), let alone have that default behavior be automagically available for impl Default types. For starters, let's focus on the first thing, shall we?
fn take (&mut Self) -> Self is the usual .take() signature, but to be object safe / dyn safe / dyn compatible, we will need to return with pointer indirection (here, in an owned fashion, say, a Box). Moreover, -> Box<Self> is still not dyn safe, since it mentions the Self type outside the self receiver position. So we directly return a Box<dyn 'lt + Trait>. Remains the question of the lifetime. For now, let's use 'static and add 'static bounds where necessary:
// "no (interior) lifetimes allowed" / I don't want to deal with lifetimes (yet)
// vvvvvvv
trait Trait : 'static {
fn some_method (self: &'_ Self)
-> String
;
fn take (self: &'_ mut Self)
-> Box<dyn 'static + Trait>
;
}
At this point, we can both write a simplified version of our objective function ('lt = 'static), and write an implementor to make sure it works:
Click to expand
fn take (obj: &'_ mut (dyn 'static + Trait))
-> Box<dyn 'static + Trait>
{
obj.take()
}
impl<T> Trait for T
where
T : 'static,
T : ::core::fmt::Display,
T : Default,
{
fn some_method (self: &'_ T)
-> String
{
ToString::to_string(self)
}
fn take (self: &'_ mut T)
-> Box<dyn 'static + Trait>
{
Box::new(::core::mem::take(self))
}
}
fn main ()
{
let mut obj: Box<dyn Trait> = Box::new(42);
println!("{}", obj.some_method()); / /42
println!("{}", take(&mut *obj).some_method()); // 42
println!("{}", obj.some_method()); // 0
}
Let's tackle now the lifetime generalization: it's actually not that hard: we can yield a dyn 'lt out of a type which is impl 'lt, i.e., a type T (or Self) whereby T : 'lt. Which 'lt? Well, we don't know, so let's let Rust / the caller choose it for us
At that point, we are already able to write the fully lifetime-generic signature we wanted: Playground
Now only remains the core thing we'd like to feature: we'd like not to have to write that "stupid" Box::new(mem::take(self)) implementation each time we are dealing with an implementation for a Default type.
Let's start with the intuitive way of writing it: in the future / nightly we will / are able to write partial implementations whereby providing default function bodies. While they key idea here, especially given the feature at play here, is about the implementation being partial (at least as far as the impl block is concerned), sadly, the currently chosen word for this is default, which is confusing given the other usages of default with the specialization feature. So default impl it is:
#![feature(specialization)]
default /* partial */
impl<T> Trait for T
where
T : Default,
{
fn take<'lt> (self: &'_ mut Self)
-> Box<dyn 'lt + Trait>
where
Self : 'lt,
{
Box::new(::core::mem::take(self))
}
}
/* later on … */
impl… Trait for … {
fn some_method (…) -> … { … }
/* no need to provide `take` ! */
}
But about non-nightly / stable Rust? We may recall that trait definitions can already provide default implementation of methods, so we may be tempted to add a Default supertrait / bound on Trait, and then write our default impl:
This works, but for having made our trait cease to be dyn safe! Indeed, Default : Sized, so the Default supertrait lead to a Sized supertrait, which is the quintessence of non-dyn-safety
But this is where the OP's observation comes into play!
Indeed, Default requires Sizedat the trait level, but we can imagine / envision an equivalent trait, but for requiring Sized at the method level:
trait DynSafeDefault {
fn default ()
-> Self
where
Self : Sized,
;
}
impl<T : Default> DynSafeDefault for T {
fn default ()
-> T
{
Default::default()
}
}
Replacing the Default supertrait with DynSafeDefault now avoids the dyn-safety issue, but now we are "back" at take()'s default implementation within the trait definition leading a paradox:
we don't want a Sized bound to be there that would prevent usage of the trait in unSized contexts, such as dyn Traits;
we need the Sized bound for the default impl.
So we are "stuck", but that's just because we have focused too much on providing the default impl within the "frontend" / user-facing trait (Trait). If we forget about the more complex take() method, and just focus on DynSafeDefault, can we go even further and remove the Sized bound on fn default()? (form this point onwards, we no longer relate that much to the OP's observation, but I think it's still an interesting one that allows to toy with moving bounds from:
the trait,
to the methods,
or even to the impl!
That last point is the key observation: at the beginning of our tests, we did manage to provide some generic implementors of our dyn Trait, so there is no reason not to be able to do the same thing here!
Apply this to our use case: key idea, replace dyn DynSafeDefault with dyn Trait, and now that we are at it, directly replace default()'s signature with take()'s. It may look weird to have a supertrait refer to a subtrait in the signature, but it's actually the trick that makes everything work!
trait TraitTake {
fn take<'slf> (self: &'_ mut Self)
-> Box<dyn 'slf + Trait>
where
Self : 'slf,
;
}
/// The keen observer may realize that this is our `default impl`
/// block from nightly.
impl<T : Default> TraitTake for T
where
T : Trait, // if we impl the subtrait and Default, then we have `.take()`
{
fn take<'slf> (self: &'_ mut T)
-> Box<dyn 'slf + Trait>
where
Self : 'slf,
{
Box::new(::core::mem::take(self))
}
}
trait Trait : TraitTake {
fn some_method (self: &'_ Self)
-> String
;
}
/// Still works.
fn take<'lt> (obj: &'_ mut (dyn 'lt + Trait))
-> Box<dyn 'lt + Trait>
{
obj.take()
}
impl Trait for i32 {
fn some_method (self: &'_ Self)
-> String
{
self.to_string()
}
}
impl Trait for () {
fn some_method (self: &'_ Self)
-> String
{
"I'm the One!".into()
}
}
let mut obj: Box<dyn Trait> = Box::new(42);
assert_eq!(obj.some_method(), "42");
drop(take(&mut *obj));
assert_eq!(obj.some_method(), "0");
obj = Box::new(());
assert_eq!(obj.some_method(), "I'm the One!");