Object-Safety and Cloning Traits

Hi Rust friends!

I have a (intermediate?) noob question! In Go, this would simply be an array of Interface types. But this is much more difficult in Rust.

I'm writing a niche kind of parser. I have a set of identical signature methods which I need implemented on a number of structural types, each with a different implementation.

trait MyTrait {
    fn transition(&self, c: char) -> MatchResult; // Match Result type omitted as irrelevant. 
    fn show_debug(&self) -> String;
}

I also want to use Bodil's im crate, particularly her OrdSet type. Now, there's a type bound on the OrdSet's element that they must implement Clone. Since Clone is not object-safe, does this fundamentally mean that you cannot store trait objects in this data structure no matter what you do? Only algebraic and structural types? I can't even have a structural type

Is there really no way for me to wrap MyTrait so I can store it in an OrdSet? My understanding is that Clone is incompatible with trait objects because Clone requires that the implementation not be object-safe; that is, any trait object instance might have a different size from any other instance, so rustc can't determine how much memory on the stack is need to store return the result of a call to .clone().

However in my case, each implementation of MyTrait is sized (they are all enums or structural types by definition).

I tried the following:

  1. First, I had MyTrait inherit from Clone
trait MyTrait: Clone {
    fn transition(&self, c: char) -> MatchResult; // Match Result type omitted as irrelevant. 
    fn show_debug(&self) -> String;
}

This fails because

the trait `MyTrait` cannot be made into an object
= note: the trait cannot require that `Self : Sized`
  1. Next, I added the Sized bound
trait MyTrait: Clone + Sized {
    fn transition(&self, c: char) -> MatchResult; // Match Result type omitted as irrelevant. 
    fn show_debug(&self) -> String;
}

This fails for the same reason with the same error message. It is unclear to me why this fails, since I've now explicitly said that the trait can require that Self: Sized

  1. Finally, I tried to add a trait bound to my OrdSet type.
struct Wrapper {
    my_set: OrdSet<MyTrait + Clone>,
}

This produces the error

error[E0225]: only auto traits can be used as additional traits in a trait object
 --> src/main.rs:4:24
  |
4 |     array: Vec<MyTrait+Clone>,
  |                        ^^^^^ non-auto additional trait

which seems to indicate that these kinds of ad-hoc bounds aren't permitted on trait object instances.

I know that I can wrap all implementations of MyTrait in an enum to make them both sized and clone-able, but I want upstream modules to be able to pass in their own implementation of MyTrait.

Thank you very much for your feedback and patience.

2 Likes

Because of the Clone bound on MyTrait, it is no longer object safe, this means that you can't do OrdSet<MyTrait>, because you are then trying to create a trait object, which is invalid. Also note that OrdSet cannot handle !Sized values like trait objects because it is missing the ?Sized bound. You will have to put the trait object behind a pointer.


It is idiomatic to write trait objects as dyn Trait to signsl that this is a trait object which will be dynamically dispatched.

2 Likes

Hi @RustyYato! Thanks for the reply. I think I understand.

I think you're saying that my OrdSet type should be parameterized as OrdSet<Box<dyn MyTrait>>, which is cloneable as long as MyTrait implements Clone. Is that correct?

Thanks again!

Ack! I think I'm still wrong. MyTrait still can't implement Clone because it's still not object-safe.

Clone is not object safe, so no matter what you can't clone a trait object. In general, if a trait has a Sized bound, even transitively, then it cannot be made into a trait object.

Do you need Clone only to satisfy the bound on OrdSet, you could use Rc<dyn MyTrait>, and omit the Clone bound on MyTrait. This works because Rc inplements Clone. If you need mutation then you can use a RefCell to regain mutation, Rc<RefCell<dyn MyTrait>>.

1 Like

Thank you! This helped. I've got it working now.

In case anyone else comes to this post with a similar struggle, I found this article helpful in explaining Krishna's response.

3 Likes

My dyn-clone crate makes it possible to have Box<dyn Trait> trait objects which are clonable without going down the whole Rc / Arc / RefCell rabbit hole.

1 Like