References to traits in structs

neil danson picture neil danson · Oct 6, 2014 · Viewed 18.9k times · Source

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

Answer

Levans picture Levans · Oct 6, 2014

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.