12

I'm trying to clone a vector of boxed traits. Naturally simply deriving Clone on all the structs that implement my trait isn't enough, because the compiler doesn't know at compile time that all the structs implementing the trait have Clone.

Okay, so I then tried to use Clone as a supertrait, but that just lead to the error in the title. I'm at a loss for solutions.

Here's the Minimal Working Implementation (or not working, since I can't clone)

#![allow(dead_code, unused_macros)]
use std::fmt::Debug;

trait MusicElement: Debug + Clone {
    fn duration(&self) -> f32;
}

#[derive(Debug, Clone)]
struct Note<'a> {
    name: &'a str,
    duration: f32,
}

impl<'a> MusicElement for Note<'a> {
    fn duration(&self) -> f32 {
        self.duration
    }
}

#[derive(Debug, Clone)]
struct Pause {
    duration: f32,
}

impl MusicElement for Pause {
    fn duration(&self) -> f32 {
        self.duration
    }
}

#[derive(Debug, Clone)]
struct Sequence {
    elements: Vec<Box<MusicElement>>,
}

impl MusicElement for Sequence {
    fn duration(&self) -> f32 {
        self.elements.iter().map(|e| e.duration()).sum()
    }
}

fn main() {
    let a4 = |dur| Box::new(Note { name: "a4", duration: dur });
    let seq = Sequence { elements: vec![a4(0.25), a4(0.25), a4(0.5)] };
    println!("{:?}", seq);
    let seq2 = seq.clone();
    println!("{:?}", seq2);
}

With this error:

error[E0038]: the trait `MusicElement` cannot be made into an object
  --> src/main.rs:33:5
   |
33 |     elements: Vec<Box<MusicElement>>,
   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `MusicElement` cannot be made into an object
   |
   = note: the trait cannot require that `Self : Sized`

And here's a link to the playground for easy code running.

I've also tried to make the elements vector in Sequence a Vec<Box<MusicElement + Clone>>, but that didn't work either.

I haven't been able to find any useful solutions online, so here's my question: How do I make the code cloneable?

Lukas Kalbertodt
  • 79,749
  • 26
  • 255
  • 305
Electric Coffee
  • 11,733
  • 9
  • 70
  • 131
  • Possible duplicate: [How to clone a struct storing a boxed trait object?](https://stackoverflow.com/questions/30353462/how-to-clone-a-struct-storing-a-boxed-trait-object). @Electric Coffee: does this help you? – Lukas Kalbertodt Apr 25 '18 at 09:08
  • I get an error saying `Note<'a>` doesn't satisfy the static lifetime – Electric Coffee Apr 25 '18 at 09:47
  • 1
    Why don't u use `String` as name? – Boiethios Apr 25 '18 at 11:08
  • Is implementing `Clone` really necessary for your business logic? This error is caused due to the Rust compiler needing to know the concrete return type of `Clone::clone` at compile time, which isn't possible with the trait object `Box` (which uses dynamic dispatch). [Read more on Object-safety violation here](https://doc.rust-lang.org/book/second-edition/ch17-02-trait-objects.html#object-safety-is-required-for-trait-objects) – jonny Apr 25 '18 at 11:37
  • 1
    unfortunately, yes. The Sequence struct lets me create deeply nested chains of MusicElements, which the program needs to be able to handle. For the sake of my own sanity, I'd prefer if I make a copy of one such sequence, rather than being forced to write it anew from scratch – Electric Coffee Apr 25 '18 at 15:09

2 Answers2

11

The solution lies in combining the suggestions in the comments thus far - the answer in @Lukas Kalbertodt's comment tells you that you must create a blanket trait implementation for all compatible ('static + MusicElement + Clone) types. The only subsequent step necessary for your implementation is changing the Note.name field's type from &'a str to String, as metioned by @Boiethios:

#![allow(dead_code, unused_macros)]
use std::fmt::Debug;

trait MusicElement: MusicElementClone + Debug {
    fn duration(&self) -> f32;
}

trait MusicElementClone {
    fn clone_box(&self) -> Box<MusicElement>;
}

impl<T: 'static + MusicElement + Clone> MusicElementClone for T {
    fn clone_box(&self) -> Box<MusicElement> {
        Box::new(self.clone())
    }
}

impl Clone for Box<MusicElement> {
    fn clone(&self) -> Box<MusicElement> {
        self.clone_box()
    }
}

#[derive(Debug, Clone)]
struct Note {
    name: String,
    duration: f32,
}

impl MusicElement for Note {
    fn duration(&self) -> f32 {
        self.duration
    }
}

#[derive(Debug, Clone)]
struct Pause {
    duration: f32,
}

impl MusicElement for Pause {
    fn duration(&self) -> f32 {
        self.duration
    }
}

#[derive(Debug, Clone)]
struct Sequence {
    elements: Vec<Box<MusicElement>>,
}

impl MusicElement for Sequence {
    fn duration(&self) -> f32 {
        self.elements.iter().map(|e| e.duration()).sum()
    }
}

fn main() {
    let a4 = |dur| Box::new(Note { name: String::from("a4"), duration: dur });
    let seq = Sequence { elements: vec![a4(0.25), a4(0.25), a4(0.5)] };
    println!("{:?}", seq);
    let seq2 = seq.clone();
    println!("{:?}", seq2);
}

This compiles, so it should suffice!

jonny
  • 3,022
  • 1
  • 17
  • 30
4

My dyn-clone crate provides a reusable implementation of jonny's answer. With it you can make your original code work with a bare minimum of changes.


Before:

trait MusicElement: Debug + Clone {
    fn duration(&self) -> f32;
}

After:

use dyn_clone::DynClone;

trait MusicElement: Debug + DynClone {
    fn duration(&self) -> f32;
}

dyn_clone::clone_trait_object!(MusicElement);

// Everything else as you wrote it.
Community
  • 1
  • 1
dtolnay
  • 9,621
  • 5
  • 41
  • 62