Djent
Djent

Reputation: 3467

Returning an object that implements trait from a method

I have a trait defined in an external crate, and I need to return it in a method from a struct that I've defined. There's no problem to accept the trait type as an input argument, but I don't know how can I return it. The trait doesn't implement Sized and I can't change its implementation.

Here's a sample code (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));
}

The error I get:

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`

If I'll comment-out the new_implementor() method, the call_get_name() method works fine accepting the trait. I've tried Boxing the returned object, but it is not possible without the Sized trait.

Is there any way I can overcome it?

// Edit

I've kinda messed up with my explanation and example. Let me phrase it again.

I want to use Peripheral struct from btleplug crate in my struct. On Linux, this struct is public but inside a private module. Only the Peripheral trait is exposed in the api module.

Here's a sample code:

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

The error I get:

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`

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

Upvotes: 1

Views: 1070

Answers (2)

Kevin Reid
Kevin Reid

Reputation: 43773

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

you are getting the type dependencies backwards: you are assuming an arbitrary type for PeripheralType (that's what impl<PeripheralType> means) and then trying to use a value of a specific but unnameable type for it.

(Side note: unnameable types also appear when using closures in Rust — each closure definition has a unique unnameable type — so this is not an unusual problem.)

Instead, what you need to do to make this work is first get the value and then make the struct for it. First, here's a definition of get_device that should work, because impl Peripheral exactly describes the situation of "I have a trait implementation but I'm not saying which one":

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

Then using this, you can construct your struct using this return value.

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

There's a catch to this, however: you can't ever write down the type of my_struct because it contains an unnameable parameter. If you need to do that, then I think you will have to instead go with dynamic dispatch:

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

With this type, there is no type parameter to give you trouble. (You'll need to write Box::new(central...unwrap()) to initialize the struct field.) The catch is that passing device to something that expects a certain peripheral type won't work.

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

That code works because it is fully generic; it doesn't have a get_device that's trying to make the peripheral type more specific than "whatever my type parameter is".


Old answer text for the first attempt at the question

This function cannot work, regardless of how you try to implement it:

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

When you write -> T inside impl<T: SomeTrait> you are saying this method will always return T for all Ts that implement SomeTrait. But that's not what you're doing; you're returning two different specific types which are not guaranteed to be equal to T.

The fundamental problem here is that you're currently trying to choose a type parameter (T) based on a value (first), which is not possible. The solution is to use the static type information, which you can do by writing your own trait and implementations:

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

Once you have this factory, you can then have ImplementorManager use it wherever it wants:

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

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

Note that the bool parameter is gone, because the type of ImplementorManager you're using entirely determines which implementor is constructed. It's a little annoying to call new_implementor, however, because you need to write out the type parameter:

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

This problem goes away when you start actually using an ImplementorManager value, in methods with self, because the type can be carried along by using Self:

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

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

On the other hand, if you actually want to have Implementor1 and Implementor2 in the same ImplementorManager, then all the <T>s are unwanted and you need to use the Box<dyn Trait> approach instead. That won't work directly because SomeTrait: Clone and Clone is not object-safe, but you can add a wrapper trait which forwards to SomeTrait but hides the Clone part:

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

Then ImplementorManager is a straightforward use of 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(),
            }),
        }
    }
}

Upvotes: 1

edkeveked
edkeveked

Reputation: 18381

By using making new_implementor a trait that is implemented by each object:

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

Everything will look like the following:

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

Upvotes: 0

Related Questions