¿Cómo clonar una estructura que almacena un objeto de rasgo en caja?

Resuelto Denis Kreshikhin asked hace 9 años • 3 respuestas

Escribí un programa que tiene el rasgo Animaly la estructura Dogque implementan el rasgo. También tiene una estructura AnimalHouseque 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 houseel 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 AnimalHousey 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 AnimalHouseclonable? ¿Es idiomático que Rust utilice un objeto de rasgo de forma activa, en general?

Denis Kreshikhin avatar May 20 '15 22:05 Denis Kreshikhin
Aceptado

Hay algunos problemas. La primera es que no hay nada que requiera que an Animaltambién implemente Clone. Podrías solucionar este problema cambiando la definición del rasgo:

trait Animal: Clone {
    /* ... */
}

Esto haría Animalque 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.

DK. avatar May 20 '2015 15:05 DK.

Mi dyn-clonecaja 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 DynClonecomo superrasgo de Animal, que requiere que cada implementación animal sea clonable.
  • Una línea para generar una implementación de la biblioteca estándar Clonepara Box<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();
}
dtolnay avatar Jun 10 '2018 16:06 dtolnay

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 Cloney 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 Rcpuntero inteligente en lugar de a Box: el objeto de rasgo no necesita implementarse Cloneya 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>.

attdona avatar Mar 27 '2018 14:03 attdona