Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Can't clone Vec<Box<Trait>> because Trait cannot be made into an object

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?

like image 730
Electric Coffee Avatar asked Apr 25 '18 08:04

Electric Coffee


2 Answers

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!

like image 170
jonny Avatar answered Nov 05 '22 04:11

jonny


My objekt 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:

#[macro_use]
extern crate objekt;

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

clone_trait_object!(MusicElement);

// Everything else as you wrote it.
like image 34
dtolnay Avatar answered Nov 05 '22 05:11

dtolnay