Devolver un objeto que implementa un rasgo de un método

Oct 03 2020

Tengo un rasgo definido en una caja externa y necesito devolverlo en un método de una estructura que he definido. No hay problema para aceptar el tipo de rasgo como argumento de entrada, pero no sé cómo puedo devolverlo. El rasgo no se implementa Sizedy no puedo cambiar su implementación.

Aquí hay un código de muestra (área de juegos ):

use std::fmt::Debug;

// this code is defined in an external crate
pub trait SomeTrait: Clone + Debug {
    fn get_name(&self) -> &str;
}

#[derive(Clone, Debug)]
struct Implementor1(String);

impl SomeTrait for Implementor1 {
    fn get_name(&self) -> &str {
        &self.0
    }
}

#[derive(Clone, Debug)]
struct Implementor2 {
    name: String,
}

impl SomeTrait for Implementor2 {
    fn get_name(&self) -> &str {
        &self.name
    }
}

// the code below is mine
struct ImplementorManager<T: SomeTrait> {
    implementors: Vec<T>,
}

impl<T: SomeTrait> ImplementorManager<T> {
    pub fn call_get_name(implementor: T) -> String {
        implementor.get_name().to_string()
    }

    pub fn new_implementor(first: bool, name: &str) -> T {
        match first {
            true => Implementor1(name.to_string()),
            false => Implementor2 {
                name: name.to_string(),
            },
        }
    }
}

fn main() {
    let implementor = Implementor1("first".to_string());
    println!("name: {}", ImplementorManager::call_get_name(implementor));
}

El error me sale:

error[E0308]: mismatched types
  --> src/main.rs:40:21
   |
33 | impl<T: SomeTrait> ImplementorManager<T> {
   |      - this type parameter
...
38 |     pub fn new_implementor(first: bool, name: &str) -> T {
   |                                                        - expected `T` because of return type
39 |         match first {
40 |             true => Implementor1(name.to_string()),
   |                     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ expected type parameter `T`, found struct `Implementor1`
   |
   = note: expected type parameter `T`
                      found struct `Implementor1`

Si comento el new_implementor()método, el call_get_name()método funciona bien aceptando el rasgo. He intentado Boxing el objeto devuelto, pero no es posible sin el Sizedrasgo.

¿Hay alguna forma de superarlo?

// Editar

Me he equivocado un poco con mi explicación y ejemplo. Déjame expresarlo de nuevo.

Quiero usar la Peripheralestructura de btleplugcrate en mi estructura. En Linux, esta estructura es pública pero dentro de un módulo privado. Solo el Peripheralrasgo se expone en el módulo api.

Aquí tienes un código de muestra:

use btleplug::api::{BDAddr, Central, Peripheral};
use btleplug::bluez::manager::Manager;
use btleplug::Error;
use std::str::FromStr;

// cannot import the Peripheral struct as the module is private
// use btleplug::bluez::adapter::peripheral::Peripheral;

struct MyStruct<PeripheralType: Peripheral> {
    device: PeripheralType,
}

impl<PeripheralType> MyStruct<PeripheralType>
where
    PeripheralType: Peripheral,
{
    fn get_device() -> PeripheralType {
        let central = Manager::new()
            .unwrap()
            .adapters()
            .unwrap()
            .into_iter()
            .next()
            .unwrap()
            .connect()
            .unwrap();
        central
            .peripheral(BDAddr::from_str("2A:00:AA:BB:CC:DD").unwrap())
            .unwrap()
    }

    pub fn new() -> Self {
        let device = Self::get_device();
        Self { device }
    }
}

fn main() -> Result<(), Error> {
    let _ = MyStruct::new();

    Ok(())
}

El error me sale:

error[E0308]: mismatched types
  --> src/main.rs:27:9
   |
13 |   impl<PeripheralType> MyStruct<PeripheralType>
   |        -------------- this type parameter
...
17 |       fn get_device() -> PeripheralType {
   |                          -------------- expected `PeripheralType` because of return type
...
27 | /         central
28 | |             .peripheral(BDAddr::from_str("2A:00:AA:BB:CC:DD").unwrap())
29 | |             .unwrap()
   | |_____________________^ expected type parameter `PeripheralType`, found struct `btleplug::bluez::adapter::peripheral::Peripheral`
   |
   = note: expected type parameter `PeripheralType`
                      found struct `btleplug::bluez::adapter::peripheral::Peripheral`

De alguna manera parece funcionar internamente , pero no entiendo por qué no funciona en mi ejemplo ...

Respuestas

1 KevinReid Oct 03 2020 at 23:06

En este código:

impl<PeripheralType> MyStruct<PeripheralType>
where
    PeripheralType: Peripheral,
{
    fn get_device() -> PeripheralType {
        ...
        central
            .peripheral(BDAddr::from_str("2A:00:AA:BB:CC:DD").unwrap())
            .unwrap()
    }

está obteniendo las dependencias de tipo al revés: está asumiendo un tipo arbitrario para PeripheralType(eso es lo que impl<PeripheralType>significa) y luego tratando de usar un valor de un tipo específico pero innombrable para él.

(Nota al margen: los tipos innombrables también aparecen cuando se usan cierres en Rust; cada definición de cierre tiene un tipo innombrable único, por lo que este no es un problema inusual).

En su lugar, lo que debe hacer para que esto funcione es primero obtener el valor y luego crear la estructura correspondiente. Primero, aquí hay una definición de get_deviceeso debería funcionar, porque impl Peripheraldescribe exactamente la situación de "Tengo una implementación de un rasgo, pero no estoy diciendo cuál":

// This should NOT be in an `impl<PeripheralType>` block.

fn get_device() -> impl Peripheral {
    let central = Manager::new()
        .unwrap()
        .adapters()
        .unwrap()
        .into_iter()
        .next()
        .unwrap()
        .connect()
        .unwrap();
    central
        .peripheral(BDAddr::from_str("2A:00:AA:BB:CC:DD").unwrap())
        .unwrap()
}

Luego, usando esto, puede construir su estructura usando este valor de retorno .

fn main() {
    let device = get_device();
    let my_struct = MyStruct { device };
    my.do_something();
}

Sin embargo, hay una trampa: nunca se puede escribir el tipo de my_structporque contiene un parámetro sin nombre. Si necesita hacer eso, entonces creo que tendrá que ir con el envío dinámico:

struct MyStruct {
    device: Box<dyn Peripheral>,
}

Con este tipo, no hay ningún parámetro de tipo que le cause problemas. (Necesitará escribir Box::new(central...unwrap())para inicializar el campo de estructura). El problema es que pasar device a algo que espera un cierto tipo de periférico no funcionará.

De alguna manera parece funcionar internamente , pero no entiendo por qué no funciona en mi ejemplo ...

Ese código funciona porque es completamente genérico; no tiene un get_deviceque intente hacer que el tipo de periférico sea más específico que "cualquiera que sea mi parámetro de tipo".


Texto de respuesta antiguo para el primer intento de la pregunta

Esta función no puede funcionar, independientemente de cómo intente implementarla:

impl<T: SomeTrait> ImplementorManager<T> {
    ...
    pub fn new_implementor(first: bool, name: &str) -> T {
        match first {
            true => Implementor1(...),
            false => Implementor2 {...},
        }
    }
}

Cuando escribe -> Tadentro impl<T: SomeTrait>, está diciendo que este método siempre regresará Tpara todos Tlos correos electrónicos que implementen SomeTrait. Pero eso no es lo que estás haciendo; está devolviendo dos tipos específicos diferentes que no se garantiza que sean iguales T.

El problema fundamental aquí es que actualmente está intentando elegir un parámetro de tipo ( T) basado en un valor ( first), lo cual no es posible. La solución es utilizar la información de tipo estático, lo que puede hacer escribiendo su propio rasgo e implementaciones:

trait SomeTraitFactory: SomeTrait {
    fn new(name: &str) -> Self;
}

impl SomeTraitFactory for Implementor1 {
    fn new(name: &str) -> Self {
        Implementor1(name.to_string())
    }
}

impl SomeTraitFactory for Implementor2 {
    fn new(name: &str) -> Self {
        Implementor2 {
            name: name.to_string(),
        }
    }
}

Una vez que tenga esta fábrica, podrá ImplementorManagerusarla donde quiera:

impl<T: SomeTraitFactory> ImplementorManager<T> {
    ...

    pub fn new_implementor(name: &str) -> T {
        <T as SomeTraitFactory>::new(name)
    }
}

Tenga en cuenta que el boolparámetro se ha ido, porque el tipo ImplementorManagerque está utilizando determina por completo qué implementador se construye. Sin new_implementorembargo, es un poco molesto llamar porque necesita escribir el parámetro de tipo:

<ImplementorManager<Implementor2>>::new_implementor("second")

Este problema desaparece cuando comienzas a usar un ImplementorManager valor, en los métodos con self, porque el tipo se puede llevar a cabo usando Self:

impl<T: SomeTraitFactory> ImplementorManager<T> {
    ...

    pub fn push_implementor(&mut self, name: &str) {
        self.implementors.push(Self::new_implementor(name));
    }
}

Por otro lado, si realmente desea tener Implementor1y Implementor2en lo mismo ImplementorManager, entonces todos los <T>mensajes de correo electrónico no son deseados y debe usar el Box<dyn Trait>enfoque en su lugar. Eso no funcionará porque directamente SomeTrait: Cloney Cloneno es seguro a objetos, pero se puede añadir un rasgo envoltorio que remite a SomeTrait, pero esconde la Cloneparte:

trait SomeTraitWrapper: Debug {
    fn get_name(&self) -> &str;
}
impl<T: SomeTrait> SomeTraitWrapper for T {
    fn get_name(&self) -> &str {
        SomeTrait::get_name(self)
    }
}

Entonces ImplementorManageres un uso sencillo de dyn:

struct ImplementorManager {
    implementors: Vec<Box<dyn SomeTraitWrapper>>,
}

impl ImplementorManager {
    pub fn call_get_name(implementor: Box<dyn SomeTraitWrapper>) -> String {
        implementor.get_name().to_string()
    }
    
    pub fn new_implementor(first: bool, name: &str) -> Box<dyn SomeTraitWrapper> {
        match first {
            true => Box::new(Implementor1(name.to_string())),
            false => Box::new(Implementor2 {
                name: name.to_string(),
            }),
        }
    }
}
edkeveked Oct 03 2020 at 18:54

Al usar la creación de new_implementorun rasgo que se implementa en cada objeto:

fn new_implementor<U: SomeTrait>(x: U) -> U
where
    U: DoSomething,
{
    x.do_something()
}

Todo se verá así:

use std::fmt::Debug;

pub trait SomeTrait: Clone + Debug {
    fn get_name(&self) -> &str;
}

#[derive(Clone, Debug)]
struct Implementor1(String);

impl Implementor1 {
    fn new(a: &str) -> Implementor1 {
        Self(a.to_string())
    }
}

impl SomeTrait for Implementor1 {
    fn get_name(&self) -> &str {
        &self.0
    }
}

#[derive(Clone, Debug)]
struct Implementor2 {
    name: String,
}

impl SomeTrait for Implementor2 {
    fn get_name(&self) -> &str {
        &self.name
    }
}

trait DoSomething {
    fn do_something(&self) -> Self
    where
        Self: SomeTrait;
    // T: SomeTrait;
}

impl DoSomething for Implementor1 {
    fn do_something(&self) -> Implementor1 {
        Implementor1::new(&self.0)
    }
}

impl DoSomething for Implementor2 {
    fn do_something(&self) -> Implementor2 {
        Self {
            name: self.name.to_string(),
        }
    }
}

// the code below is mine
struct ImplementorManager<T: SomeTrait> {
    implementors: Vec<T>,
}

impl<T: SomeTrait> ImplementorManager<T> {
    pub fn call_get_name(implementor: T) -> String {
        implementor.get_name().to_string()
    }

    fn new_implementor<U: SomeTrait>(x: U) -> U
    where
        U: DoSomething,
    {
        x.do_something()
    }
}

fn main() {
    let implementor2 = Implementor2 {
        name: "test".to_string(),
    };
    let implementor1 = Implementor1("test".to_string());
    println!(
        "name: {:?}",
        ImplementorManager::<Implementor2>::new_implementor(implementor2)
    );
    println!(
        "name: {:?}",
        ImplementorManager::<Implementor1>::new_implementor(implementor1)
    );
}

patio de recreo