¿Cómo clonar una estructura que almacena un objeto de rasgo en caja?
Escribí un programa que tiene el rasgo Animal
y la estructura Dog
que implementan el rasgo. También tiene una estructura AnimalHouse
que almacena un animal como objeto de rasgo Box<Animal>
.
trait Animal {
fn speak(&self);
}
struct Dog {
name: String,
}
impl Dog {
fn new(name: &str) -> Dog {
return Dog {
name: name.to_string(),
};
}
}
impl Animal for Dog {
fn speak(&self) {
println!{"{}: ruff, ruff!", self.name};
}
}
struct AnimalHouse {
animal: Box<Animal>,
}
fn main() {
let house = AnimalHouse {
animal: Box::new(Dog::new("Bobby")),
};
house.animal.speak();
}
Devuelve "Bobby: ¡ruff, ruff!" como se esperaba, pero si intento clonar house
el compilador devuelve errores:
fn main() {
let house = AnimalHouse {
animal: Box::new(Dog::new("Bobby")),
};
let house2 = house.clone();
house2.animal.speak();
}
error[E0599]: no method named `clone` found for type `AnimalHouse` in the current scope
--> src/main.rs:31:24
|
23 | struct AnimalHouse {
| ------------------ method `clone` not found for this
...
31 | let house2 = house.clone();
| ^^^^^
|
= help: items from traits can only be used if the trait is implemented and in scope
= note: the following trait defines an item `clone`, perhaps you need to implement it:
candidate #1: `std::clone::Clone`
Intenté agregar #[derive(Clone)]
antes struct AnimalHouse
y obtuve otro error:
error[E0277]: the trait bound `Animal: std::clone::Clone` is not satisfied
--> src/main.rs:25:5
|
25 | animal: Box<Animal>,
| ^^^^^^^^^^^^^^^^^^^ the trait `std::clone::Clone` is not implemented for `Animal`
|
= note: required because of the requirements on the impl of `std::clone::Clone` for `std::boxed::Box<Animal>`
= note: required by `std::clone::Clone::clone`
¿ Cómo hago que la estructura sea AnimalHouse
clonable? ¿Es idiomático que Rust utilice un objeto de rasgo de forma activa, en general?
Hay algunos problemas. La primera es que no hay nada que requiera que an Animal
también implemente Clone
. Podrías solucionar este problema cambiando la definición del rasgo:
trait Animal: Clone {
/* ... */
}
Esto haría Animal
que los objetos ya no fueran seguros, lo que significa que Box<dyn Animal>
dejará de ser válido, por lo que no es genial.
Lo que puedes hacer es insertar un paso adicional. Para ello (con adiciones del comentario de @ChrisMorgan ).
trait Animal: AnimalClone {
fn speak(&self);
}
// Splitting AnimalClone into its own trait allows us to provide a blanket
// implementation for all compatible types, without having to implement the
// rest of Animal. In this case, we implement it for all types that have
// 'static lifetime (*i.e.* they don't contain non-'static pointers), and
// implement both Animal and Clone. Don't ask me how the compiler resolves
// implementing AnimalClone for dyn Animal when Animal requires AnimalClone;
// I have *no* idea why this works.
trait AnimalClone {
fn clone_box(&self) -> Box<dyn Animal>;
}
impl<T> AnimalClone for T
where
T: 'static + Animal + Clone,
{
fn clone_box(&self) -> Box<dyn Animal> {
Box::new(self.clone())
}
}
// We can now implement Clone manually by forwarding to clone_box.
impl Clone for Box<dyn Animal> {
fn clone(&self) -> Box<dyn Animal> {
self.clone_box()
}
}
#[derive(Clone)]
struct Dog {
name: String,
}
impl Dog {
fn new(name: &str) -> Dog {
Dog {
name: name.to_string(),
}
}
}
impl Animal for Dog {
fn speak(&self) {
println!("{}: ruff, ruff!", self.name);
}
}
#[derive(Clone)]
struct AnimalHouse {
animal: Box<dyn Animal>,
}
fn main() {
let house = AnimalHouse {
animal: Box::new(Dog::new("Bobby")),
};
let house2 = house.clone();
house2.animal.speak();
}
Al introducir clone_box
, podemos solucionar los problemas al intentar clonar un objeto de rasgo.
Mi dyn-clone
caja implementa una versión reutilizable de la respuesta de DK . Con él puedes hacer que tu código original funcione con un mínimo de cambios.
- Una línea para agregar
DynClone
como superrasgo deAnimal
, que requiere que cada implementación animal sea clonable. - Una línea para generar una implementación de la biblioteca estándar
Clone
paraBox<dyn Animal>
.
// [dependencies]
// dyn-clone = "1.0"
use dyn_clone::{clone_trait_object, DynClone};
trait Animal: DynClone {
fn speak(&self);
}
clone_trait_object!(Animal);
#[derive(Clone)]
struct Dog {
name: String,
}
impl Dog {
fn new(name: &str) -> Dog {
Dog { name: name.to_owned() }
}
}
impl Animal for Dog {
fn speak(&self) {
println!{"{}: ruff, ruff!", self.name};
}
}
#[derive(Clone)]
struct AnimalHouse {
animal: Box<dyn Animal>,
}
fn main() {
let house = AnimalHouse {
animal: Box::new(Dog::new("Bobby")),
};
let house2 = house.clone();
house2.animal.speak();
}
Editar (dric512) : actualizando ligeramente (arreglando) esta respuesta. Responde en parte a la pregunta, pero sigue siendo una respuesta útil, con limitaciones:
- Al clonar la estructura principal, el rasgo dinámico no se clona, solo se incrementa el recuento de referencias. Esto no se puede utilizar si es necesario duplicar el objeto dinámico.
- Como resultado, el rasgo no necesita extenderse
Clone
y se convierte en objeto de seguridad.
La respuesta anterior responde correctamente a la pregunta sobre el almacenamiento de un objeto de rasgo en caja.
Saliéndonos del tema con respecto al título, pero no sobre la forma idiomática de usar objetos de rasgo, una solución alternativa podría ser usar el Rc
puntero inteligente en lugar de a Box
: el objeto de rasgo no necesita implementarse Clone
ya que clonar la estructura significa incrementar el recuento de referencias. sobre el objeto:
trait Animal {
fn speak(&self);
}
...
#[derive(Clone)]
struct AnimalHouse {
animal: Rc<dyn Animal>,
}
fn main() {
let house = AnimalHouse { animal: Rc::new(Dog::new("Bobby")) };
let house2 = house.clone();
house2.animal.speak();
}
Nota : Rc<T>
solo se utiliza en escenarios de un solo subproceso; también hay Arc<T>
.