从方法返回实现特征的对象

时间:2020-10-03 07:05:13

标签: rust

我在外部包装箱中定义了一个特征,我需要从我定义的结构中的方法中将其返回。接受trait类型作为输入参数没有问题,但是我不知道如何返回它。该特征未实现Sized,并且我无法更改其实现。

这是示例代码(playground):

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

我得到的错误:

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`

如果我要注释掉new_implementor()方法,则call_get_name()方法可以很好地接受特征。我已经尝试Box处理返回的对象,但是如果没有Sized特性,则不可能。

有什么办法可以克服吗?

//编辑

我有点弄乱了我的解释和例子。让我再说一遍。

我想在结构中使用Peripheral条板箱中的btleplug结构。在Linux上,此结构是公共的,但在私有模块中。 api模块中仅公开Peripheral特性。

这是示例代码:

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

我得到的错误:

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`

在某种程度上work internally似乎如此,但是我不明白为什么在我的示例中它不起作用...

2 个答案:

答案 0 :(得分:1)

在此代码中:

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

您正在倒退类型依赖关系:您假设PeripheralType为任意类型(这就是impl<PeripheralType>的意思),然后尝试为其使用特定但无法命名的类型的值。< / p>

(附带说明:在Rust中使用 closures 时也会出现无法命名的类型-每个闭包定义都有一个唯一的无法命名的类型-因此这不是常见的问题。)

相反,要做此工作所需要做的是首先 获取值,然后然后为其构造结构。首先,这里是get_device的定义应该起作用,因为impl Peripheral准确描述了“我有一个特质实现,但我没有说哪个”的情况:

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

然后使用它,您可以使用此返回值构造您的结构

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

但是,有一个陷阱:您永远无法写下my_struct的类型,因为它包含一个无法命名的参数。如果您需要这样做,那么我认为您将不得不使用动态调度:

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

使用这种类型,没有类型参数会给您带来麻烦。 (您需要编写Box::new(central...unwrap())来初始化struct字段。)要注意的是,将device 传递给期望某些外围设备类型的东西将不起作用。 / p>

在某种程度上work internally似乎如此,但是我不明白为什么在我的示例中它不起作用...

该代码有效,因为它是完全通用的;它没有get_device试图使外围设备类型比“我的类型参数是什么”更具体。


第一次尝试回答该问题的旧答案文本

无论您如何尝试实现此功能,都无法使用:

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

当您在-> T中写入impl<T: SomeTrait>时,是说对于实现T的所有T,此方法将始终返回SomeTrait。 / em>但这不是您在做什么;您将返回两个不同的特定类型,这些类型保证等于T

这里的基本问题是您当前正在尝试基于值(T)选择类型参数(first),这是不可能的。解决方案是使用静态类型信息,您可以通过编写自己的特征和实现来实现:

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

一旦有了这个工厂,就可以让ImplementorManager在任何需要的地方使用它:

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

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

请注意,bool参数已消失,因为您使用的ImplementorManager的类型完全决定了构造哪个实现器。但是,调用new_implementor有点烦人,因为您需要写出type参数:

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

当您在使用ImplementorManager的方法中实际开始使用self 时,此问题就消失了,因为可以使用Self来携带类型:

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

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

另一方面,如果您实际上想将Implementor1Implementor2放在同一个ImplementorManager中,那么所有<T>都是多余的,则需要使用而是使用Box<dyn Trait>方法。这不会直接起作用,因为SomeTrait: CloneClone不是对象安全的,但是您可以添加一个包装特征,该特征转发到SomeTrait但隐藏Clone部分:< / p>

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

然后ImplementorManagerdyn的直接用法:

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

答案 1 :(得分:0)

使用new_implementor作为每个对象实现的特征:

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

所有内容都将如下所示:

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

playground