I have two independent libraries (distinct crates), each defining a struct and a function to build such a struct, if possible (for simplicity, I put everything into one module):
struct Foo {}
struct Bar {}
impl Foo {
fn make_foo(a: &str) -> Option<Foo> {
Some(Foo {})
}
}
impl Bar {
fn make_bar(b: &str) -> Option<Bar> {
Some(Bar {})
}
}
I now want to handle the two structs in the same way, independent of whether I actually have a Foo
or a Bar
:
trait SomeTrait {
fn do_something() -> ();
}
impl SomeTrait for Foo {
fn do_something() -> () {
()
}
}
impl SomeTrait for Bar {
fn do_something() -> () {
()
}
}
Since the size of SomeTrait
is unknown at compile time, I can't simply let a: Option<SomeTrait> = Foo::make_foo("abc")
. So I tried wrap it in a Box
(before unpacking the Option
):
fn main() {
let f: Option<Box<SomeTrait>> = Foo::make_foo("abc").map(Box::new)
}
But still the compiler complains:
error: mismatched types [E0308]
let b: Option<Box<SomeTrait>> = Foo::new().map(Box::new);
^~~~~~~~~~~~~~~~~~~~~~~~
help: run `rustc --explain E0308` to see a detailed explanation
note: expected type `std::option::Option<Box<SomeTrait>>`
note: found type `std::option::Option<Box<Foo>>`
I tried to use as
for casting, but that didn't work since these are all non-scalar types.
How do I solve this (without touching the implementation of Foo
and Bar
)? I feel like I'm trying to apply patterns from classical OOP-languages to Rust's trait system.
In the end, I want to be able to do something like:
fn main() {
let arg = 5;
let x: Option<Box<SomeTrait>> = match arg {
4 => Foo::make_foo("abc").map(Box::new),
_ => Bar::make_bar("abc").map(Box::new),
};
x.do_something()
}
Full example:
// Library part
struct Foo {}
struct Bar {}
impl Foo {
fn new() -> Option<Foo> {
Some(Foo {})
}
}
impl Bar {
fn new() -> Option<Bar> {
Some(Bar {})
}
}
// My Part
trait SomeTrait {
fn do_something(&self);
}
impl SomeTrait for Foo {
fn do_something(&self) {
println!("foo")
}
}
impl SomeTrait for Bar {
fn do_something(&self) {
println!("bar")
}
}
fn main() {
let b: Option<Box<SomeTrait>> = match "x" {
"x" => Foo::new().map(Box::new),
_ => Bar::new().map(Box::new),
};
b.unwrap().do_something();
}
There are two general problems with this. The first is you need to have the object represented as a Option<Box<SomeTrait>>
so setting your variable binding needs to be something like this let f: Option<Box<SomeTrait>> = Some(Box::new(Foo::make_foo("abc")));
and you need to remove the option part from your make method so you can wrap it properly later.
impl Foo {
fn make_foo(a: &str) -> Foo {
Foo {}
}
}
The ordering here is important. Option can't hold an unsized trait so it needs to wrap the box.
Second is, a trait can not become a trait object if the trait has methods that don't reference &self
A quick except from the rust error docs shows
Method has no receiver
Methods that do not take a self parameter can't be called since there won't be a way to get a pointer to the method table for them.
So for your trait methods you need
trait SomeTrait {
fn do_something(&self);
}
impl SomeTrait for Foo {
fn do_something(&self) { }
}
impl SomeTrait for Bar {
fn do_something(&self) { }
}
Edit:
Since you can not change the make functions, another approach for mapping them properly is adding a return type to the lambda so it knows how to help the compiler agree with the type.
let f: Option<Box<SomeTrait>> = Foo::make_foo("abc").and_then(|foo| -> Option<Box<SomeTrait>> {
Some(Box::new(foo))
});
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With