Let's say I have some trait:
trait MyTrait {
fn function1(&self);
}
and some type that implements it:
struct MyStruct {
number: i32,
}
impl MyTrait for MyStruct {
fn function1(&self) {
printn!("{}", self.number);
}
}
Now I have another type, which wants to take things that implement MyTrait
. It doesn't care whether they're owned or not. From reading around, it sounds like the right way to accomplish this is to have it take Borrow<X>
instead of X
or &X
or whatever. This lets it take things of type X
, or Rc<X>
or Box<X>
, etc...
I've got this working when X
is a concrete type, but how do I make it work when X
is a trait?
Here's what I tried first:
struct Consumer<T> {
inner: T
}
impl<T: Borrow<MyTrait>> Consumer<T> {
pub fn new(inner: T) -> Consumer<T> {
Consumer {
inner: inner
}
}
pub fn do_stuff(&self) {
self.inner.borrow().function1();
}
}
fn main() {
// I want to eventually be able to swap this out for x = Rc::new(MyStruct ...
// but I'll keep it simple for now.
let x = MyStruct { number: 42 };
let c = Consumer::new(x);
c.do_stuff();
}
This doesn't work yet, because MyStruct
implements Borrow<MyStruct>
, but not Borrow<MyTrait>
. Okay, so let's try to implement that:
impl Borrow<MyTrait> for MyStruct {
fn borrow(&self) -> &MyTrait {
self
}
}
This gives me the following error, which I don't understand:
<anon>:33:5: 35:6 error: method `borrow` has an incompatible type for trait: expected bound lifetime parameter , found concrete lifetime [E0053] <anon>:33 fn borrow(&self) -> &MyTrait { <anon>:34 self <anon>:35 } <anon>:33:5: 35:6 help: see the detailed explanation for E0053 error: aborting due to previous error playpen: application terminated with error code 101
What? There aren't any concrete lifetimes mentioned at all in there, and Borrow
is defined without any lifetimes mentioned either. I'm stumped.
Firstly, are my assumptions correct that using Borrow
is the right way to go? And if so, how do I implement Borrow
of a trait?
The correct way to write the implementation is this:
impl<'a> Borrow<MyTrait + 'a> for MyStruct {
fn borrow(&self) -> &(MyTrait + 'a) {
self
}
}
Trait objects can be restricted with a lifetime bound. That's because a type that implements a trait might contain references, and in some situations, we need to be able to differentiate an object that depends on borrowed objects from an object that doesn't. If the lifetime bound is not specified, I think it defaults to 'static
; however, specifying &(MyTrait + 'static)
as the return type compiles (it's less generic, so you should favor the generic solution above), so the issue you encountered is more subtle than that...
As an aside, I found an alternative way to do this, that doesn't require implementing Borrow<MyTrait>
at all:
Instead of having impl<T: Borrow<MyTrait> Consumer<T>
, we can make Consumer
take an additional parameter that designates what the actual borrowed type will be, and then constrain that type to implement the trait. Like this:
impl<T: Borrow<Borrowed>, Borrowed: MyTrait> Consumer<T, Borrowed> {
This requires Consumer
to have a PhantomData
member that references the otherwise unused Borrowed
type parameter. Here's a full implementation:
struct Consumer<T, Borrowed> {
inner: T,
phantom: PhantomData<Borrowed>
}
impl<T: Borrow<Borrowed>, Borrowed: MyTrait> Consumer<T, Borrowed> {
fn new(inner: T) -> Consumer<T, Borrowed> {
Consumer {
inner: inner,
phantom: PhantomData
}
}
pub fn do_stuff(&self) { // this function is the same as before.
self.inner.borrow().function1();
}
}
This alternative has the nice property that it allows using traits with generic methods inside them, because it doesn't require creating any trait objects (trait objects can't be made for traits that have generic functions: see https://doc.rust-lang.org/error-index.html#method-has-generic-type-parameters).
The one downside is that Consumer
now has to be given hints as to its generic parameters. You have to tell it the concrete type involved:
fn main() {
let x = MyStruct { number: 42 };
let c = Consumer::<_, MyStruct>::new(x);
c.do_stuff();
}
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