简体   繁体   中英

Implementing Borrow<Trait> for a type that implements Trait

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

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM