Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

References to traits in structs

Tags:

rust

traits

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....

  1. Why doesn't & 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?
  2. Why can't I dereference self in get_foo() - All examples I've seen use the borrowed self syntax?
  3. What's the implication of removing the & 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() {} 
like image 601
neil danson Avatar asked Oct 06 '14 08:10

neil danson


People also ask

Are Rust traits interfaces?

Rust is not an object oriented language. And traits are not exactly interfaces.

What is a trait object?

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.

What is trait Rust?

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.

How do you create a trait object?

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.


2 Answers

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.

like image 90
Levans Avatar answered Sep 29 '22 10:09

Levans


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

like image 22
Arien Malec Avatar answered Sep 29 '22 10:09

Arien Malec