1

I'm new to Rust traits, so this could be due to a misunderstanding of supertraits, dyn or anything else. I'm trying to use a trait object in an enum to:

  • Put a trait bound on the concrete types that can be used in this element of the enum
  • Make sure that the enum can still derive Copy

The minimal example (which fails to compile on Rust playground with the relevant error) is:

#[derive(Copy)]
enum Foo {
    A,
    B(dyn MyTraitWhichIsCopy),
}

trait MyTraitWhichIsCopy: Copy {}

The error is:

error[E0204]: the trait `Copy` may not be implemented for this type
 --> src/lib.rs:1:10
  |
1 | #[derive(Copy)]
  |          ^^^^
...
4 |     B(dyn MyTraitWhichIsCopy),
  |       ---------------------- this field does not implement `Copy`
  |
  = note: this error originates in a derive macro (in Nightly builds, run with -Z macro-backtrace for more info)

error: aborting due to previous error

For more information about this error, try `rustc --explain E0204`.

After invoking rustc --explain E0204 I noticed the following, which might be where I'm hitting problems:

The `Copy` trait is implemented by default only on primitive types. If your
type only contains primitive types, you'll be able to implement `Copy` on it.
Otherwise, it won't be possible.

Is there a way to accomplish what I'm trying to do?

Kyle_S-C
  • 1,107
  • 1
  • 14
  • 31

2 Answers2

5

There are several problems in the given code.

  1. The trait Clone is not object-safe because the method clone refers to the raw self type, which is unknown through the dyn abstraction. Your trait has a super trait Copy and Copy has a super trait Clone, therefore your trait is not object-safe, which means it cannot be used as a trait object, which is also known as "dyn trait".

  2. Trait object is ?Sized which means it is a dynamic sized type (DST, whose size is unknown at compile time) that cannot be used as a enum field because it also makes the enum type ?Sized. In most cases, DST can only be held through reference, practically Box<dyn Trait> when owned, otherwise &dyn Trait or &mut dyn Trait.

  3. Copy requires super trait Clone, which means "any type that implements Copy must implement Clone". So when we wish our custom type to implement Copy, we should implement Clone first.

Conclusion: It is impossible to constraint a type to implement Copy or Clone through a trait object, and you must use a Box to hold your trait object, and use a manually defined clone instead of the standard clone trait and implement clone for the boxed trait object:

trait MyTrait {
    fn clone_dyn(&self) -> Box<dyn MyTrait>;
}

#[derive(Clone)]
struct MyImpl;

impl MyTrait for MyImpl {
    fn clone_dyn(&self) -> Box<dyn MyTrait> {
        Box::new(self.clone())
    }
}

impl Clone for Box<dyn MyTrait> {
    fn clone(&self) -> Self {
        self.clone_dyn()
    }
}

#[derive(Clone)]
enum Foo {
    A,
    B(Box<dyn MyTrait>),
}
Alsein
  • 4,268
  • 1
  • 15
  • 35
  • Thanks - your reference to object safety was something I hadn't seen before and pointed me towards this [other helpful discussion](https://users.rust-lang.org/t/object-safety-and-cloning-traits/22620) – Kyle_S-C Apr 25 '21 at 09:46
  • 1
    There is an [official document](https://doc.rust-lang.org/reference/items/traits.html#object-safety). @Kyle_S-C – Alsein Apr 25 '21 at 09:49
1

You can use a generic type bound by your trait. Also notice that you need Clone to have Copy too:

#[derive(Clone, Copy)]
enum Foo<T: MyTraitWhichIsCopy> {
    A,
    B(T),
}

trait MyTraitWhichIsCopy: Copy {}

Playground

Netwave
  • 40,134
  • 6
  • 50
  • 93
  • Thanks - I think there's another implicit requirement that I omitted - that you don't end up with separate instances of the enum, depending on which type you declare it with. Accordingly, I think Alsein's answer is closer to the problem I have. – Kyle_S-C Apr 25 '21 at 09:35