“Expected trait A, found &A” when trying to box a trait object
“Expected trait A, found &A” when trying to box a trait object
I'm trying to make a trait that can either retrieve (and return a reference to) a trait object of another trait, or create one (and return a boxed version of it), leaving the choice to the implementor (which means I need to restrict the returned object's lifetime to that of the producer). However, I'm running into errors:
use std::borrow::Borrow;
use std::collections::HashMap;
trait A
fn foobar(&self)
println!("!");
trait ProducerOrContainer
fn get_a<'a>(&'a self, name: &'a str) -> Option<Box<dyn A + 'a>>;
impl<'b, B: Borrow<A>> ProducerOrContainer for HashMap<&'b str, B>
fn get_a<'a>(&'a self, name: &'a str) -> Option<Box<dyn A + 'a>> borrow
The error is:
error[E0308]: mismatched types
--> src/main.rs:20:9
|
20 | self.get(name).map(|borrow| Box::new(borrow.borrow()))
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ expected trait A, found &A
|
= note: expected type `std::option::Option<std::boxed::Box<dyn A + 'a>>`
found type `std::option::Option<std::boxed::Box<&dyn A>>`
Which puzzles me, because I'd expect a &A
to be an A
too. I've tried to impl<'a> A for &'a A
, but that doesn't help either. Is there any way to fix this?
&A
A
impl<'a> A for &'a A
A
&A
foo.bar()
(*foo).bar()
Why would want to return a
Box<&dyn A>
anyway? Boxing a reference doesn't seem very useful.– Peter Hall
Sep 16 '18 at 10:53
Box<&dyn A>
I noticed that
name: &'a str
is unnecessarily constrained here, because the returned reference is not tied to name
. Maybe you have other implementations of ProducerOrContainer
where the returned value is derived from name
, but if not, getting rid of the 'a
will be more flexible.– trentcl
Sep 16 '18 at 13:06
name: &'a str
name
ProducerOrContainer
name
'a
2 Answers
2
...that can either retrieve (and return a reference to) a trait object of another trait, or create one (and return a boxed version of it).
With this requirement, a Box
will not work. A Box
owns its data, but you sometimes have borrowed data, which you can't move.
Box
Box
There is a type in the standard library called Cow
, which is an abstraction over whether a value is borrowed or owned. However, it may not be quite suitable for you here because it won't let you own the data as a Box
and it also requires that your data type must implement ToOwned
.
Cow
Box
ToOwned
But we can take your requirement and model it directly as an enum
:
enum
enum BoxOrBorrow<'a, T: 'a + ?Sized>
Boxed(Box<T>),
Borrowed(&'a T),
And make it ergonomic to use by implementing Deref
:
Deref
use std::ops::Deref;
impl<'a, T> Deref for BoxOrBorrow<'a, T>
type Target = T;
fn deref(&self) -> &T
match self
BoxOrBorrow::Boxed(b) => &b,
BoxOrBorrow::Borrowed(b) => &b,
This lets you treat the custom BoxOrBorrow
type as any other reference - you can dereference it with *
or pass it to any function that expects a reference to T
.
BoxOrBorrow
*
T
This is what your code would look like:
trait ProducerOrContainer
fn get_a<'a>(&'a self, name: &'a str) -> Option<BoxOrBorrow<'a, dyn A + 'a>>;
impl<'b, B: Borrow<dyn A>> ProducerOrContainer for HashMap<&'b str, B>
fn get_a<'a>(&'a self, name: &'a str) -> Option<BoxOrBorrow<'a, dyn A + 'a>>
self.get(name)
.map(
You can make the original code compile by implementing A
for &'_ dyn A
and adding an explicit cast:
A
&'_ dyn A
self.get(name).map(|borrow| Box::new(borrow.borrow()) as Box<dyn A>)
A closure is not a coercion site. The compiler looks at the contents of the closure to see what the return value is, and concludes that it returns Box<&'a dyn A>
. But the closure itself cannot be coerced from "function returning Box<&'a dyn A>
" to "function returning Box<dyn A + 'a>
", because those types are structurally different. You add the cast to tell the compiler that you wanted the closure to return Box<dyn A>
in the first place.
Box<&'a dyn A>
Box<&'a dyn A>
Box<dyn A + 'a>
Box<dyn A>
But this is a bit silly. Box
ing a reference is completely unnecessary here, and casting it to Box<dyn A>
just adds another level of indirection for the caller. It would be better to return a type that encapsulates the idea of "either a boxed trait object, or a reference to a trait object", as Peter Hall's answer describes.
Box
Box<dyn A>
In a future version of Rust, with generic associated types ("GATs"), it will be possible to make the return type an associated type of ProducerOrContainer
, something like the following:
ProducerOrContainer
trait ProducerOrContainer
type Result<'a>: A;
fn get_a<'a>(&'a self, name: &'a str) -> Option<Result<'a>>;
With this trait definition, each type that implements ProducerOrContainer
can choose what type it returns, so you can pick Box<dyn A>
for some impl
s and &'a dyn A
for others. However, this is not possible in current Rust (1.29).
ProducerOrContainer
Box<dyn A>
impl
&'a dyn A
Thanks for contributing an answer to Stack Overflow!
But avoid …
To learn more, see our tips on writing great answers.
Required, but never shown
Required, but never shown
By clicking "Post Your Answer", you agree to our terms of service, privacy policy and cookie policy
A
implementing a trait does not mean that&A
does. It sometimes can seem like that's true because of Rust's auto-ref and auto-deref behaviour. For example, it will auto-deref a variable so you can typefoo.bar()
instead of(*foo).bar()
.– Peter Hall
Sep 16 '18 at 10:42