Lista de genericos

Necesito almacenar una lista de traits objects pero el trait tiene un genérico, pero al crear la variable animales me dice que le debo colocar el tipo, y necesito hacer algo parecido dónde el tipo pues sean varios y dependiendo de la implementación, que en la implementación este la información del tipo

trait Animal<T> {
    fn hablar(&self) -> String;

    fn obtener_valor_asociado(&self) -> T;
}

enum Especie {
    Perro,
    Gato,
}

struct Perro {
    nombre: String,
    edad: i32,
    especie: Especie,
}

impl Animal<i32> for Perro {
    fn hablar(&self) -> String {
        format!("Guau, mi nombre es {} y tengo {} años", self.nombre, self.edad)
    }

    fn obtener_valor_asociado(&self) -> i32 {
        self.edad
    }
}

struct Gato {
    nombre: String,
    color: String,
    especie: Especie,
}

impl Animal<String> for Gato {
    fn hablar(&self) -> String {
        format!("Miau, mi nombre es {} y soy de color {}", self.nombre, self.color)
    }

    fn obtener_valor_asociado(&self) -> String {
        self.color.clone()
    }
}

fn main() {
    let mut animales: Vec<Box<dyn Animal>> = Vec::new();

    animales.push(Box::new(Perro {
        nombre: "Rex".to_string(),
        edad: 5,
        especie: Especie::Perro,
    }));
    animales.push(Box::new(Gato {
        nombre: "Misha".to_string(),
        color: "negro".to_string(),
        especie: Especie::Gato,
    }));

    for animal in animales {
        println!("{} tiene un valor asociado de {}", animal.hablar(), animal.obtener_valor_asociado());
    }
}

Verás la versión en español de mi respuesta abajo, la cual fue creada usando traducción automática.


No hablo español, así que estoy utilizando una traducción automática para entender mejor tu pregunta:

I can’t speak Spanish, so I’m making a machine translation so I can understand your question better:

I need to store a list of trait objects, but the trait has a generic. However, when I create the animals variable, it tells me that I must specify the type. I need to do something where the type can be various, depending on the implementation, such that the type information is specified in the implementation.

trait Animal<T> {
    fn speak(&self) -> String;

    fn get_associated_value(&self) -> T;
}

enum Species {
    Dog,
    Cat,
}

struct Dog {
    name: String,
    age: i32,
    species: Species,
}

impl Animal<i32> for Dog {
    fn speak(&self) -> String {
        format!("Woof, my name is {} and I am {} years old", self.name, self.age)
    }

    fn get_associated_value(&self) -> i32 {
        self.age
    }
}

struct Cat {
    name: String,
    color: String,
    species: Species,
}

impl Animal<String> for Cat {
    fn speak(&self) -> String {
        format!("Meow, my name is {} and my color is {}", self.name, self.color)
    }

    fn get_associated_value(&self) -> String {
        self.color.clone()
    }
}

fn main() {
    let mut animals: Vec<Box<dyn Animal>> = Vec::new();

    animals.push(Box::new(Dog {
        name: "Rex".to_string(),
        age: 5,
        species: Species::Dog,
    }));
    animals.push(Box::new(Cat {
        name: "Misha".to_string(),
        color: "black".to_string(),
        species: Species::Cat,
    }));

    for animal in animals {
        println!("{} has an associated value of {}", animal.speak(), animal.get_associated_value());
    }
}

Answer (English)

There are two problems with abstracting over Animals like you are trying to do. A type parameter like for Animals<T> allows the type T not only to depend on the implementation, as you noted, too, but it even allows multiple implementations with different parameters. I.e. you can create something like

impl Animal<i32> for Dog {
    fn speak(&self) -> String {
        format!("Woof, my name is {} and I am {} years old", self.name, self.age)
    }

    fn get_associated_value(&self) -> i32 {
        self.age
    }
}

// This does not create a conflict / overlap
impl Animal<String> for Dog {
    fn speak(&self) -> String {
        format!("Woof, my name is {} and I am a Dog", self.name)
    }

    fn get_associated_value(&self) -> String {
        self.name.clone()
    }
}

What you might be wanting here is instead an associated type:

trait Animal {
    type Value; // The associated type declaration.
    
    fn speak(&self) -> String;

    fn get_associated_value(&self) -> Self::Value;
}

impl Animal for Dog {
    type Value = i32;

    fn speak(&self) -> String {
        format!("Woof, my name is {} and I am {} years old", self.name, self.age)
    }

    fn get_associated_value(&self) -> Self::Value {
        self.age
    }
}

impl Animal for Cat {
    type Value = String;

    fn speak(&self) -> String {
        format!("Meow, my name is {} and my color is {}", self.name, self.color)
    }

    fn get_associated_value(&self) -> Self::Value {
        self.color.clone()
    }
}

(playground)

An associated type really means that “the type information is specified in the implementation”, i.e. the implementation chooses the type.

It still doesn’t fully address the trait object issue though. For dyn Animal even like this, one still needs to actually write dyn Animal<Value = …something…>.

To hide that information, you need to re-introduce some common interface. In this case, your use-case seems to be that printing the type should be possible, so maybe something like Box<dyn Display> could work. If you don't want to modify your trait further, you can adapt it with a different trait trait objects of which you'll then use:

use std::fmt;

trait GeneralAnimalWithDisplayValue {
    // different name can help avoid ambiguities when used
    fn general_speak(&self) -> String;

    fn general_get_associated_value(&self) -> Box<dyn fmt::Display>;
}

impl<T> GeneralAnimalWithDisplayValue for T
where
    T: Animal,
    T::Value: fmt::Display + 'static,
{
    fn general_speak(&self) -> String {
        self.speak()
    }

    fn general_get_associated_value(&self) -> Box<dyn fmt::Display> {
        Box::new(self.get_associated_value())
    }
}

fn main() {
    let mut animals: Vec<Box<dyn GeneralAnimalWithDisplayValue>> = Vec::new();

    animals.push(Box::new(Dog {
        name: "Rex".to_string(),
        age: 5,
        species: Species::Dog,
    }));
    animals.push(Box::new(Cat {
        name: "Misha".to_string(),
        color: "black".to_string(),
        species: Species::Cat,
    }));

    for animal in animals {
        println!("{} has an associated value of {}", animal.general_speak(), animal.general_get_associated_value());
    }
}

(playground)

Alternatively, one could even make a helper Wrapper and use the original Animal trait, but with Value = Box<dyn fmt::Display>:

use std::fmt;

struct Wrapper<T>(T);

impl<T: Animal> Animal for Wrapper<T>
where
    T::Value: fmt::Display + 'static,
{
    type Value = Box<dyn fmt::Display>;

    fn speak(&self) -> String {
        self.0.speak()
    }

    fn get_associated_value(&self) -> Self::Value {
        Box::new(self.0.get_associated_value())
    }
}

fn main() {
    let mut animals: Vec<Box<dyn Animal<Value = Box<dyn fmt::Display>>>> = Vec::new();

    animals.push(Box::new(Wrapper(Dog {
        name: "Rex".to_string(),
        age: 5,
        species: Species::Dog,
    })));
    animals.push(Box::new(Wrapper(Cat {
        name: "Misha".to_string(),
        color: "black".to_string(),
        species: Species::Cat,
    })));

    for animal in &animals {
        println!("{} has an associated value of {}", animal.speak(), animal.get_associated_value());
    }
}

(playground)

Alternatively, going even further into the direction of “specific helper trait for this specific use-case” you can skip the need for boxing into Box<dyn fmt::Display> and instead put the implementation of the for loop body into a trait:

use std::fmt;

trait AnimalHelperTrait {
    fn main_loop_body(&self);
}
impl<T> AnimalHelperTrait for T
where
    T: Animal,
    T::Value: fmt::Display,
{
    fn main_loop_body(&self) {
        println!("{} has an associated value of {}", self.speak(), self.get_associated_value());
    }
}

fn main() {
    let mut animals: Vec<Box<dyn AnimalHelperTrait>> = Vec::new();

    animals.push(Box::new(Dog {
        name: "Rex".to_string(),
        age: 5,
        species: Species::Dog,
    }));
    animals.push(Box::new(Cat {
        name: "Misha".to_string(),
        color: "black".to_string(),
        species: Species::Cat,
    }));

    for animal in animals {
        animal.main_loop_body();
    }
}

(playground)

Also, as an unrelated note, I think your species: Species field seems rather useless, given the type itself already describes whether it’s a Cat or Dog.

Respuesta (Español)

Hay dos problemas al intentar abstraer sobre Animales como lo estás intentando hacer. Un parámetro de tipo como para Animales<T> permite que el tipo T no solo dependa de la implementación, como también notaste, sino que incluso permite múltiples implementaciones con diferentes parámetros. Es decir, puedes crear algo como:

impl Animal<i32> for Perro {
    fn hablar(&self) -> String {
        format!("Guau, mi nombre es {} y tengo {} años", self.nombre, self.edad)
    }

    fn obtener_valor_asociado(&self) -> i32 {
        self.edad
    }
}

// Esto no crea un conflicto / solapamiento
impl Animal<String> for Perro {
    fn hablar(&self) -> String {
        format!("Guau, mi nombre es {} y soy un Perro", self.nombre)
    }

    fn obtener_valor_asociado(&self) -> String {
        self.nombre.clone()
    }
}

Lo que podrías querer aquí es en cambio un tipo asociado:

trait Animal {
    type Valor; // La declaración del tipo asociado.
    
    fn hablar(&self) -> String;

    fn obtener_valor_asociado(&self) -> Self::Valor;
}

impl Animal for Perro {
    type Valor = i32;

    fn hablar(&self) -> String {
        format!("Guau, mi nombre es {} y tengo {} años", self.nombre, self.edad)
    }

    fn obtener_valor_asociado(&self) -> Self::Valor {
        self.edad
    }
}

impl Animal for Gato {
    type Valor = String;

    fn hablar(&self) -> String {
        format!("Miau, mi nombre es {} y mi color es {}", self.nombre, self.color)
    }

    fn obtener_valor_asociado(&self) -> Self::Valor {
        self.color.clone()
    }
}

(playground)

Un tipo asociado realmente significa que “en la implementación este la información del tipo”, es decir, la implementación elige el tipo.

Aún así, esto no aborda completamente el problema de los objetos de tipo trait. Para dyn Animal incluso así, todavía se necesita escribir dyn Animal<Valor = …algo…>.

Para ocultar esa información, necesitas reintroducir alguna interfaz común. En este caso, tu caso de uso parece ser que sería posible imprimir el tipo, así que tal vez algo como Box<dyn Display> podría funcionar. Si no quieres modificar tu trait más, puedes adaptarlo con un diferente trait cuyos objetos de trait entonces usarás:

use std::fmt;

trait AnimalGeneralConValorMostrable {
    // un nombre diferente puede ayudar a evitar ambigüedades cuando se usa
    fn hablar_general(&self) -> String;

    fn obtener_valor_asociado_general(&self) -> Box<dyn fmt::Display>;
}

impl<T> AnimalGeneralConValorMostrable for T
where
    T: Animal,
    T::Valor: fmt::Display + 'static,
{
    fn hablar_general(&self) -> String {
        self.hablar()
    }

    fn obtener_valor_asociado_general(&self) -> Box<dyn fmt::Display> {
        Box::new(self.obtener_valor_asociado())
    }
}

fn main() {
    let mut animales: Vec<Box<dyn AnimalGeneralConValorMostrable>> = Vec::new();

    animales.push(Box::new(Perro {
        nombre: "Rex".to_string(),
        edad: 5,
        especie: Especie::Perro,
    }));
    animales.push(Box::new(Gato {
        nombre: "Misha".to_string(),
        color: "negro".to_string(),
        especie: Especie::Gato,
    }));

    for animal in animales {
        println!("{} tiene un valor asociado de {}", animal.hablar_general(), animal.obtener_valor_asociado_general());
    }
}

(playground)

Alternativamente, incluso podrías hacer un Wrapper auxiliar y usar el trait Animal original, pero con Valor = Box<dyn fmt::Display>:

use std::fmt;

struct Wrapper<T>(T);

impl<T: Animal> Animal for Wrapper<T>
where
    T::Valor: fmt::Display + 'static,
{
    type Valor = Box<dyn fmt::Display>;

    fn hablar(&self) -> String {
        self.0.hablar()
    }

    fn obtener_valor_asociado(&self) -> Self::Valor {
        Box::new(self.0.obtener_valor_asociado())
    }
}

fn main() {
    let mut animales: Vec<Box<dyn Animal<Valor = Box<dyn fmt::Display>>>> = Vec::new();

    animales.push(Box::new(Wrapper(Perro {
        nombre: "Rex".to_string(),
        edad: 5,
        especie: Especie::Perro,
    })));
    animales.push(Box::new(Wrapper(Gato {
        nombre: "Misha".to_string(),
        color: "negro".to_string(),
        especie: Especie::Gato,
    })));

    for animal in &animales {
        println!("{} tiene un valor asociado de {}", animal.hablar(), animal.obtener_valor_asociado());
    }
}

(playground)

Alternativamente, yendo aún más lejos en la dirección de "trait auxiliar específico para este caso de uso específico", puedes omitir la necesidad de encajar en Box<dyn fmt::Display> e en lugar de ello poner la implementación del cuerpo del bucle for en un trait:

use std::fmt;

trait TraitAuxiliarAnimal {
    fn main_cuerpo_del_bucle(&self);
}
impl<T> TraitAuxiliarAnimal for T
where
    T: Animal,
    T::Valor: fmt::Display,
{
    fn main_cuerpo_del_bucle(&self) {
        println!("{} tiene un valor asociado de {}", self.hablar(), self.obtener_valor_asociado());
    }
}

fn main() {
    let mut animales: Vec<Box<dyn TraitAuxiliarAnimal>> = Vec::new();

    animales.push(Box::new(Perro {
        nombre: "Rex".to_string(),
        edad: 5,
        especie: Especie::Perro,
    }));
    animales.push(Box::new(Gato {
        nombre: "Misha".to_string(),
        color: "negro".to_string(),
        especie: Especie::Gato,
    }));

    for animal in animales {
        animal.main_cuerpo_del_bucle();
    }
}

(playground)

Además, como una nota no relacionada, creo que tu campo especie: Especie parece bastante inútil, dado que el tipo en sí ya describe si es un Gato o un Perro.

7 Likes

Hello, thank you, the problem I had was not specifically that but it was similar, the purpose was not that, what I wanted was to have an interface where what one method returned was received by another. example..

type ObjectDataType = Box<dyn ObjectData>;
#[async_trait]
pub trait SubscriberConsumer: Send + Sync {
     async fn deserialize(&self, value: Value) -> ObjectDataType;
     async fn new_message(&self, nc: ObjectDataType);
}

and have a list like this for example

pub type SubscriberConsumerType = Arc<RwLock<Box<dyn SubscriberConsumer>>>

  let mut map: HashMap<&str, SubscriberConsumerType> = HashMap::new();

In order not to have to use the data associated with the traits, I had to create an interface or trait that implements the data that I want to move between those two methods.

This would be the object that passed between methods

pub trait ObjectData: ObjectDataClone + std::fmt::Debug + Send + Sync {
     fn as_any(&self) -> Box<dyn Any>;
}

and this would be the specific data, where the above trait is implemented

#[derive(Debug, Clone, Serialize, Deserialize, Default)]
struct Data {
     name: String,
}
impl ObjectData for Data {
     fn as_any(&self) -> Box<dyn Any> {
         Box::new(self.clone())
     }
}

then in the SubscriberConsumer implementation I do a downcast on that any object

fn cast<T: 'static>(&self, n: ObjectDataType) -> Result<T, String> {
       match n.as_any().downcast::<T>() {
           Ok(data) => Ok(*data),
           ...
 }


#[async_trait]
impl SubscriberConsumer for PrintExample {
     async fn deserialize(&self, value: Value) -> ObjectDataType {
         ...
     }

     async fn new_message(&self, nc: ObjectDataType) {
         if let Ok(data) = self.cast::<Data>(nc) {
             println!("{}", data.name);
         }
     }
}

That was my solution.

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.