I have a trait Foo
pub trait Foo { fn do_something(&self) -> f64; }
and a struct which references that trait
pub struct Bar { foo: Foo, }
Trying to compile I get
error: reference to trait `Foo` where a type is expected; try `Box<Foo>` or `&Foo`
Changing the struct to
struct Bar { foo: &Foo, }
Tells me error: missing lifetime specifier
Changing the definition to
struct Bar { foo: Box<Foo>, }
Compiles — yay!
However, when I want a function to return foo
on bar
- something like:
impl Bar { fn get_foo(&self) -> Foo { self.foo } }
Well obviously bar.foo
is a Box<Foo>
, so expectedly I get error: reference to trait `Foo` where a type is expected; try `Box<Foo>` or `&Foo`
Changing the signature to
impl Bar { fn get_foo(&self) -> Box<Foo> { let this = *self; this.foo } }
But now I get error: cannot move out of dereference of `&`-pointer
on trying to dereference self
.
Changing to
impl Bar { fn get_foo(self) -> Box<Foo> { self.foo } }
Is all good.
So....
&
in the bar
struct work? I'm assuming I have to box as structs have a set memory layout so we have to say it's a pointer to a trait (as we can't know how big that will be), but why does the compiler suggest something that wont compile?self
in get_foo()
- All examples I've seen use the borrowed self
syntax?&
and just using self
?Learning Rust is fascinating, but the memory safety is both fascinating and intimidating!
Code in full that compiles:
trait Foo { fn do_something(&self) -> f64; } struct Bar { foo: Box<Foo>, } impl Bar { fn get_foo(self) -> Box<Foo> { let foo = self.foo; foo.do_something(); foo } } fn main() {}
Rust is not an object oriented language. And traits are not exactly interfaces.
A trait object is an opaque value of another type that implements a set of traits. The set of traits is made up of an object safe base trait plus any number of auto traits. Trait objects implement the base trait, its auto traits, and any supertraits of the base trait.
A trait in Rust is a group of methods that are defined for a particular type. Traits are an abstract definition of shared behavior amongst different types. So, in a way, traits are to Rust what interfaces are to Java or abstract classes are to C++. A trait method is able to access other methods within that trait.
We create a trait object by specifying some sort of pointer, such as a & reference or a Box<T> smart pointer, then the dyn keyword, and then specifying the relevant trait.
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>
.
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); }
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(); }
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.
To note for future reference: the syntax has changed from
struct Bar<'a> { foo: &'a Foo + 'a, }
to
struct Bar<'a> { foo: &'a (Foo + 'a), // with parens }
Per RFC 438
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