What difference between trait signatures?

the one:

pub trait Decode<'a>: Sized + 'a {
    fn decode(code: u8, stream: &'a [u8]) -> Result<Self, ()>;
}

the two:

pub trait Decode<'a> {
    fn decode(code: u8, stream: &'a [u8]) -> Result<Self, ()> where Self: Sized + 'a;
}

or is it the same thing?

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>() {}

Compare this with the behavior of

pub trait Decode<'a>: Sized + 'a {
    fn decode(code: u8, stream: &'a [u8]) -> Result<Self, ()>;
}

impl<'a, 'b> Decode<'a> for &'b () {
    fn decode(code: u8, stream: &'a [u8]) -> Result<Self, ()> {
        todo!()
    }
}
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

so you must instead write

impl<'a, 'b: 'a> Decode<'a> for &'b () {…}
2 Likes

I think this is a great situation to talk about actually using / "exploiting" this difference to do something useful.

Let's say you want to write a .take() / mem::take() equivalent for trait objects:

fn take<'obj> (obj: &'_ mut (dyn 'obj + Trait))
  -> Box<dyn 'obj + Trait>

Not only do you want to write such a function, you'd like Trait to be implementable for all types that are Default (the core signature behind take).

Well, let's do it then!

  1. Define the trait, with some method (say, .to_string()) to make it somewhat meaningful:

    trait Trait {
        fn some_method (self: &'_ Self)
          -> String
        ;
    }
    
  2. 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>
        ;
    }
    
  3. 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
    }
    

  4. 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 :grin:

    - trait Trait : 'static {
    + trait Trait {
          …
    
    -     fn take (self: &'_ mut Self)
    -       -> Box<dyn 'static + Trait>
    +     fn take<'lt> (self: &'_ mut Self)
    +       -> Box<dyn 'lt + Trait>
    +     where
    +         Self : 'lt,
          ;
      }
    
  5. At that point, we are already able to write the fully lifetime-generic signature we wanted: Playground

  6. 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` ! */
    }
    
  7. 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:

    - trait Trait {
    + trait Trait : Default {
          fn some_method (self: &'_ Self)
            -> String
          ;
      
          fn take<'lt> (self: &'_ mut Self)
            -> Box<dyn 'lt + Trait>
    -     ;
    +     where
    +         Self : 'lt,
    +     {
    +         Box::new(::core::mem::take(self))
    +     }
      }
    

    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 :sweat_smile:

  8. But this is where the OP's observation comes into play!

    Indeed, Default requires Sized at 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!

    trait DynSafeDefault {
        fn default<'slf> ()
          -> Box<dyn 'slf + DynSafeDefault> /* to be fully dyn-usable */
        where
            Self : 'slf,
        ;
    }
    
    impl<T : Default> DynSafeDefault {
        fn default<'slf> ()
          -> Box<dyn 'slf + DynSafeDefault>
        where
            Self : 'slf,
        {
            Box::new(Default::default())
        }
    }
    
  9. 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!");
    
2 Likes

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.