Rückgabe eines Objekts, das das Merkmal einer Methode implementiert

Oct 03 2020

Ich habe ein Merkmal in einer externen Kiste definiert und muss es in einer Methode aus einer von mir definierten Struktur zurückgeben. Es ist kein Problem, den Merkmalstyp als Eingabeargument zu akzeptieren, aber ich weiß nicht, wie ich ihn zurückgeben kann. Das Merkmal wird nicht implementiert Sizedund ich kann seine Implementierung nicht ändern.

Hier ist ein Beispielcode ( Spielplatz ):

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));
}

Der Fehler, den ich bekomme:

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`

Wenn ich die new_implementor()Methode call_get_name()auskommentiere, funktioniert die Methode gut, wenn ich das Merkmal akzeptiere. Ich habe versucht, Boxdas zurückgegebene Objekt zu verwenden, aber es ist ohne das SizedMerkmal nicht möglich .

Kann ich es auf irgendeine Weise überwinden?

// Bearbeiten

Ich habe meine Erklärung und mein Beispiel irgendwie durcheinander gebracht. Lassen Sie es mich noch einmal sagen.

Ich möchte Peripheralstruct from btleplugcrate in meiner struct verwenden. Unter Linux ist diese Struktur öffentlich, befindet sich jedoch in einem privaten Modul. PeripheralIm API-Modul wird nur das Merkmal angezeigt.

Hier ist ein Beispielcode:

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(())
}

Der Fehler, den ich bekomme:

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`

Es scheint irgendwie intern zu funktionieren , aber ich verstehe nicht, warum es in meinem Beispiel nicht funktioniert ...

Antworten

1 KevinReid Oct 03 2020 at 23:06

In diesem Code:

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

Sie erhalten die Typabhängigkeiten rückwärts: Sie nehmen einen beliebigen Typ für an PeripheralType(das impl<PeripheralType>bedeutet) und versuchen dann, einen Wert eines bestimmten, aber nicht benennbaren Typs dafür zu verwenden.

(Randnotiz: Unbenennbare Typen werden auch angezeigt, wenn Verschlüsse in Rust verwendet werden. Jede Verschlussdefinition hat einen eindeutigen unbenennbaren Typ. Dies ist also kein ungewöhnliches Problem.)

Stattdessen müssen Sie zuerst den Wert abrufen und dann die Struktur dafür erstellen , damit dies funktioniert . Hier ist zunächst eine Definition get_device, die funktionieren sollte, da sie impl Peripheralgenau die Situation beschreibt: "Ich habe eine Implementierung von Merkmalen, aber ich sage nicht, welche":

// 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()
}

Auf diese Weise können Sie Ihre Struktur mit diesem Rückgabewert erstellen .

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

Dies hat jedoch einen Haken: Sie können den Typ von niemals aufschreiben, my_structda er einen unbenennbaren Parameter enthält. Wenn Sie das tun müssen, müssen Sie sich stattdessen für den dynamischen Versand entscheiden:

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

Bei diesem Typ gibt es keinen Typparameter, der Ihnen Probleme bereitet. (Sie müssen schreiben Box::new(central...unwrap()), um das Strukturfeld zu initialisieren.) Der Haken ist, dass die Übergabe device an etwas, das einen bestimmten Peripherietyp erwartet, nicht funktioniert.

Es scheint irgendwie intern zu funktionieren , aber ich verstehe nicht, warum es in meinem Beispiel nicht funktioniert ...

Dieser Code funktioniert, weil er vollständig generisch ist. Es gibt keine get_device, die versucht, den Peripherietyp spezifischer zu machen als "was auch immer mein Typparameter ist".


Alter Antworttext für den ersten Versuch der Frage

Diese Funktion kann nicht funktionieren, unabhängig davon, wie Sie versuchen, sie zu implementieren:

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

Wenn Sie -> Thineinschreiben impl<T: SomeTrait>, sagen Sie, dass diese Methode immer Tfür alle Ts zurückgibt, die implementiert werden SomeTrait. Aber das ist nicht was du tust; Sie geben zwei verschiedene spezifische Typen zurück, von denen nicht garantiert wird, dass sie gleich sind T.

Das grundlegende Problem hierbei ist, dass Sie derzeit versuchen, einen Typparameter ( T) basierend auf einem Wert ( first) auszuwählen , was nicht möglich ist. Die Lösung besteht darin , die statischen Typinformationen zu verwenden, die Sie durch Schreiben Ihrer eigenen Merkmale und Implementierungen ausführen können:

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(),
        }
    }
}

Sobald Sie diese Fabrik haben, können Sie sie ImplementorManagerverwenden, wo immer Sie wollen:

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

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

Beachten Sie, dass der boolParameter nicht mehr vorhanden ist, da der von ImplementorManagerIhnen verwendete Typ vollständig bestimmt, welcher Implementierer erstellt wird. Das Aufrufen new_implementorist jedoch etwas ärgerlich , da Sie den Typparameter ausschreiben müssen:

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

Dieses Problem tritt auf, wenn Sie tatsächlich einen ImplementorManager Wert in Methoden mit verwenden self, da der Typ mit folgenden Elementen übertragen werden kann Self:

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

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

Auf der anderen Seite, wenn Sie tatsächlich wollen Implementor1und Implementor2in der gleichen ImplementorManager, dann sind alle <T>s unerwünscht und Sie müssen Box<dyn Trait>stattdessen den Ansatz verwenden. Das wird nicht direkt arbeiten , weil SomeTrait: Cloneund Clonenicht objekt sicher ist, aber Sie können einen Wrapper Eigenschaft hinzufügen , die nach vorne SomeTraitaber Häuten der CloneTeil:

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

Dann ImplementorManagerist eine einfache Verwendung von 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

Indem Sie new_implementorein Merkmal erstellen, das von jedem Objekt implementiert wird:

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

Alles wird wie folgt aussehen:

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)
    );
}

Spielplatz