Returning a clonable trait

Hello and happy New Year! :fireworks:

I cannot understand why this code is not compiling:

trait Foo : Clone {}

trait Bar {
    fn get(&self) -> &dyn Foo;
}

I cannot understand why the compiler is complaining about this:

  |
1 | trait Foo : Clone {}
  |       ---   ^^^^^ ...because it requires `Self: Sized`
  |       |
  |       this trait cannot be made into an object...

Ok, Clone requires Sized, but I am returning a reference to something that will be managed via vtable (If I got it right, dyn is there just to stress this point). So why I cannot returning it? How I can solve it?

Thank you.

The problem comes when the compiler tries to figure out what slots the vtable needs. The function signature for clone is

fn clone(&self)->Self;

In the case of a trait object, Self is dyn Foo:

fn clone(&self)->dyn Foo;

Which is not allowed: function return types must be Sized for technical reasons.

Because everything that implements Foo must also implement Clone, the trait object dyn Foo must also implement Clone. This is impossible for the reasons stated above, so something has to give way. The choice made by the Rust designers is that the Clone requirement prevents the creation of any dyn Foo-- the vtable would require a function that can't exist.


One way around this is with an associated type:

trait Bar {
    type GetResult: Foo;
    fn get(&self) -> &Self::GetResult;
}

This lets each implementor of Bar choose its own Foo type to return, without the need for a vtable. IT won't let you mix and match types in the same container like dyn would, though.

3 Likes

If you really need a clone, another option is to use a trait like CloneBoxed

trait CloneBoxed {
    fn clone_boxed(&self) -> Box<dyn YourTrait>;
}

impl<T: Clone + YourTrait> CloneBoxed for T {
    fn clone_boxed(&self) -> Box<dyn YourTrait> {
        Box::new(self.clone())
    }
}

pub trait YourTrait: CloneBoxed {
    // ...
}
3 Likes

I was trying to use your suggested solution, but it gives me the following error:

error[E0310]: the parameter type `T` may not live long enough
 --> src/main.rs:7:9
  |
5 | impl<T: Clone + YourTrait> CloneBoxed for T {
  |      -- help: consider adding an explicit lifetime bound...: `T: 'static +`
6 |     fn clone_boxed(&self) -> Box<dyn YourTrait> {
7 |         Box::new(self.clone())
  |         ^^^^^^^^^^^^^^^^^^^^^^ ...so that the type `T` will meet its required lifetime bounds

error: aborting due to previous error

I have tried to add lifetime, but with no luck :roll_eyes:

Right, I forgot about lifetimes in trait objects. There are two ways to fix this solution with different tradeoffs:

https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=98555498a5002bdd1827600543e311c5

version_1 requires 'static, which is unfortunate, but version_2 is not as ergonomic in the long run, you'll likely want a BoxedTrait type alias like in the playground to make things easier.

edit: @Yandros provides a better solution below

1 Like

There is a simpler solution for the lifetime

trait CloneBoxed {
    fn clone_boxed<'slf> (self: &'_ Self)
      -> Box<dyn YourTrait + 'slf>
    where
        Self : 'slf,
    ;
}

For 'static types, Rust will already be able to pick 'slf = 'static, while also supporting non-'static types :slightly_smiling_face:

5 Likes

Thank you @Yandros !

Just a more stupid question. Why you used the where clause instead of writing?

fn close_boxed<'slf>(&'slf self) -> Box<dyn YourTrait + 'slf>;

Are they the same, aren't? :thinking:

Hi,

well as far as I'm aware Self: 'slf is not the same as &'slf self. The later one is giving the lifetime 'slf to the borrow that is passed into close_boxed, but the first one specifies the lifetime 'slf for the type Self.
The lifetime requirement for the type Self can only be expressed in the where clause as far as I know.

1 Like

While the existence of a &'slf self implies that Self: 'slf, it is more restrictive because it requires a borrow for that lifetime. For example String: 'static, however you probably won't be able to easily provide a &'static String

2 Likes

Another example is that with a &'slf self receiver, you could simply yield a &'slf (dyn YourTrait + 'slf) , no need to clone: indeed, both your suggested signature and this one require that the borrow on *self be held for the return value to be usable.

<'slf> where Self : 'slf must be read as " slf := lifetimeof(Self) (note that it is a type we are talking about). So, for Self = String, that's 'static, and for Self = &'borrow str (so that self: &'_ (&'borrow str)), that would be 'borrow, and for Struct<'a, 'b>, that would be the intersection ("minimum" of 'a, and 'b).

Honestly, that's new to me! :flushed:
I was thinking that lifetimes can be only attached to a borrows.

So if I got it right, writing a bound like Self: 'slf means "all references in Self must outlive 'slf", right?

Thank you.

Yeah. The way I like to think of it is that T: 'a means that it would be valid for a value of type T to exist anywhere inside the region denoted by 'a. In particular, this means that references in T must be valid in the region, because it is invalid to have a value with expired references.

Note that "would be valid" is quite precisely worded here, and it doesn't mean e.g. "a value of type T must live longer than 'a" or "a value of type T is valid only in 'a and not outside of 'a".

Of course here T = Self and 'a is called 'slf.

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.