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?
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!
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.
trait MusicElement: Debug + Clone {
fn duration(&self) -> f32;
}
#[macro_use]
extern crate objekt;
trait MusicElement: Debug + objekt::Clone {
fn duration(&self) -> f32;
}
clone_trait_object!(MusicElement);
// Everything else as you wrote it.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With