Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How does borrowing Box<Trait> contents work?

I have this minimal example code:

use std::borrow::BorrowMut;

trait Foo {}
struct Bar;
impl Foo for Bar {}

fn main() {
    let mut encryptor: Box<Foo> = Box::new(Bar);

    encrypt(encryptor.borrow_mut());
}

fn encrypt(encryptor: &mut Foo) { }

but it fails with this error:

error: `encryptor` does not live long enough
  --> src/main.rs:11:1
   |
10 |     encrypt(encryptor.borrow_mut());
   |             --------- borrow occurs here
11 | }
   | ^ `encryptor` dropped here while still borrowed
   |
   = note: values in a scope are dropped in the opposite order they are created

The kind people at #rustbeginners found that I have to dereference the box to get the contents, and then borrow the contents. Like this:

trait Foo {}
struct Bar;
impl Foo for Bar {}

fn main() {
    let mut encryptor: Box<Foo> = Box::new(Bar);

    encrypt(&mut *encryptor);
}

fn encrypt(encryptor: &mut Foo) { }

It works, but I don't understand it.

Why do I need to dereference first? What is the error trying to say? Normally it isn't an error that a value is dropped at the end of the function.


Apparently it's not just me who doesn't understand how this works; an issue has been filed.

like image 497
Daniel A.A. Pelsmaeker Avatar asked Jan 16 '17 18:01

Daniel A.A. Pelsmaeker


1 Answers

Let's start with a change that allows the code to work:

fn encrypt(encryptor: &mut (Foo + 'static)) { }

The important difference is the addition of + 'static to the trait object - the parens are just needed for precedence.

The important thing to recognize is that there are two lifetimes present in &Foo:

  • a lifetime for the reference itself: &'a Foo
  • a lifetime that represents all the references inside the concrete value that the trait abstracts: &(Foo + 'b).

If I'm reading the RFCs correctly, this was introduced by RFC 192, and RFC 599 specified reasonable defaults for the lifetimes. In this case, the lifetimes should expand like:

fn encrypt(encryptor: &mut Foo) { }
fn encrypt<'a>(encryptor: &'a mut (Foo + 'a)) { }

On the other end of the pipe, we have a Box<Foo>. Expanded by the rules of the RFC, this becomes Box<Foo + 'static>. When we take a borrow of it, and try to pass it to the function, we have an equation to solve:

  • The lifetime inside the trait object is 'static.
  • The function takes a reference to a trait object.
  • The lifetime of the reference equals the lifetime of references inside the trait object.
  • Therefore, the reference to the trait object must be 'static. Uh oh!

The Box will be dropped at the end of the block so it certainly isn't static.

The fix with explicit lifetimes allows the lifetime of the reference to the trait object to differ from the lifetime of the references inside the trait object.

If you needed to support a trait object with internal references, an alternate is to do something like:

fn encrypt<'a>(encryptor: &mut (Foo + 'a)) { }

True credit for this explanation goes to nikomatsakis and his comment on GitHub, I just expanded it a bit.

like image 155
Shepmaster Avatar answered Oct 26 '22 04:10

Shepmaster