Reputation: 3467
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 Box
ing the returned object, but it is not possible without the Sized
trait.
Is there any way I can overcome it?
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
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".
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 T
s 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
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)
);
}
Upvotes: 0