rust - 从方法返回实现 trait 的对象

标签 rust

我在外部 crate 中定义了一个特征,我需要从我定义的结构中的方法中返回它。接受 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 上,此结构是公共(public)的,但在私有(private)模块中。只有 Peripheral trait 暴露在 api 模块中。
这是一个示例代码:
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 ,但我不明白为什么它在我的例子中不起作用......

最佳答案

在这段代码中:

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> 的意思),然后尝试为其使用特定但无法命名的类型的值。
(旁注:在 Rust 中使用闭包时也会出现不可命名的类型——每个闭包定义都有一个唯一的不可命名类型——所以这不是一个不寻常的问题。)
相反,您需要做的是首先获取值,然后为它创建结构。首先,这是get_device 的定义。应该可以,因为 impl Peripheral准确描述了“我有一个 trait 实现,但我没有说是哪一个”的情况:
// 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期望某种外围类型的东西不起作用。

It somehow seems to work internally, but I don't understand why it doesn't work in my example...


该代码有效,因为它是完全通用的;它没有 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 的.但这不是你正在做的;您将返回两种不同的特定类型,它们不能保证等于 T .
这里的基本问题是您当前正试图根据值( T )选择类型参数( first ),这是不可能的。解决方案是使用静态类型信息,您可以通过编写自己的 trait 和实现来做到这一点:
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有点烦,但是,因为您需要写出类型参数:
<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> s 是不需要的,您需要使用 Box<dyn Trait>而是接近。这不会直接起作用,因为 SomeTrait: CloneClone不是对象安全的,但您可以添加一个包装器特征,它转发到 SomeTrait但隐藏了Clone部分:
trait SomeTraitWrapper: Debug {
    fn get_name(&self) -> &str;
}
impl<T: SomeTrait> SomeTraitWrapper for T {
    fn get_name(&self) -> &str {
        SomeTrait::get_name(self)
    }
}
然后ImplementorManager直接使用 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(),
            }),
        }
    }
}

关于rust - 从方法返回实现 trait 的对象,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/64181613/

相关文章:

rust - 为什么当我推送的元素多于 Vec 的容量时没有错误?

rust - 要求在继承特征的关联类型上绑定(bind)特征

rust - 如何将 "x-y"翻译成 vec![x, x+1, … y-1, y]?

rust - 如何在不使用 Copy 或 Clone 的情况下克隆 Rust 中的结构?

memory - 无法移出带有作为参数传递的可选成员的借用内容

rust - 如何使用引用解构元组结构

rust - 如何使用 Tokio 实现基于拉取的系统?

process - 如何在 Rust 中将线程提升为子进程?

rust - 匹配两个参数和范围

memory - Rust `String` 类型/`read_line` 函数如何在没有明确告知的情况下知道需要多少内存?