This is the tricky point of trait objects, you need to be very explicit about who owns the underlying object.
Indeed, when you use a trait as a type, the underlying object must be stored somewhere, as trait objects are in fact references to an object implementing the given trait. This is why you cannot have a bare MyTrait
as a type, it must be either a reference &MyTrait
or a box Box<MyTrait>
.
With references
The first method you tried was was with a reference and the compiler complained about a missing lifetime specifier :
struct Bar {
foo : &Foo,
}
The problem is, a reference doesn't own the underlying object, and an other object or scope must own it somewhere: you are only borrowing it. And thus, the compiler need information about how long this reference will be valid: if the underlying object was destroyed, your Bar instance would have a reference to freed memory, which is forbidden !
The idea here is to add lifetimes:
struct Bar<'a> {
foo : &'a (Foo + 'a),
}
What you are saying here to the compiler is : "My Bar object cannot outlive the Foo reference inside it". You have to specify the lifetime two times : once for the lifetime of the reference, and once for the trait object itself, because traits can be implemented for references, and if the underlying object is a reference, you must specify its lifetime as well.
On special case would be writing:
struct Bar<'a> {
foo : &'a (Foo + 'static),
}
In this case, the 'static
requires that the underlying object must be a real struct, or a &'static
reference, but other references won't be allowed.
Also, to build your object, you will have to give it a reference to an other object you store yourself.
You end up with something like this :
trait Foo {}
struct MyFoo;
impl Foo for MyFoo {}
struct Bar<'a> {
foo: &'a (Foo + 'a),
}
impl<'a> Bar<'a> {
fn new(the_foo: &'a Foo) -> Bar<'a> {
Bar { foo: the_foo }
}
fn get_foo(&'a self) -> &'a Foo {
self.foo
}
}
fn main() {
let myfoo = MyFoo;
let mybar = Bar::new(&myfoo as &Foo);
}
With Boxes
A Box contrarily owns its content, thus it allows you to give ownership of the underlying object to your Bar struct. Yet, as this underlying object could be a reference, you need to specify a lifetime as well :
struct Bar<'a> {
foo: Box<Foo + 'a>
}
If your know that the underlying object cannot be a reference, you can also write:
struct Bar {
foo: Box<Foo + 'static>
}
and the lifetime problem disappears completely.
The construction of the object is thus similar, but simpler as you don't need to store the underlying object yourself, it is handled by the box :
trait Foo {}
struct MyFoo;
impl Foo for MyFoo {}
struct Bar<'a> {
foo: Box<Foo + 'a>,
}
impl<'a> Bar<'a> {
fn new(the_foo: Box<Foo + 'a>) -> Bar<'a> {
Bar { foo: the_foo }
}
fn get_foo(&'a self) -> &'a Foo {
&*self.foo
}
}
fn main() {
let mybar = Bar::new(box MyFoo as Box<Foo>);
}
In this case, the 'static
version would be :
trait Foo {}
struct MyFoo;
impl Foo for MyFoo {}
struct Bar {
foo: Box<Foo + 'static>,
}
impl Bar {
fn new(the_foo: Box<Foo + 'static>) -> Bar {
Bar { foo: the_foo }
}
fn get_foo<'a>(&'a self) -> &'a Foo {
&*self.foo
}
}
fn main() {
let mybar = Bar::new(box MyFoo as Box<Foo>);
let x = mybar.get_foo();
}
With the bare value
To answer your last question :
Whats the implication of removing the & and just using self?
If a method has a definition like this :
fn unwrap(self) {}
It means it will consume your object in the process, and after calling bar.unwrap()
, you won't be able to use bar
any longer.
It is a process used generally to give back ownership of the data your struct owned. You'll meet a lot of unwrap()
functions in the standard library.