Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do I add references to a container when the borrowed values are created after the container?

For reasons related to code organization, I need the compiler to accept the following (simplified) code:

fn f() {
    let mut vec = Vec::new();
    let a = 0;
    vec.push(&a);
    let b = 0;
    vec.push(&b);
    // Use `vec`
}

The compiler complains

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

error: `b` does not live long enough
 --> src/main.rs:8:1
  |
6 |     vec.push(&b);
  |               - borrow occurs here
7 |     // Use `vec`
8 | }
  | ^ `b` dropped here while still borrowed
  |
  = note: values in a scope are dropped in the opposite order they are created

However, I'm having a hard time convincing the compiler to drop the vector before the variables it references. vec.clear() doesn't work, and neither does drop(vec). mem::transmute() doesn't work either (to force vec to be 'static).

The only solution I found was to transmute the reference into &'static _. Is there any other way? Is it even possible to compile this in safe Rust?

like image 990
moatPylon Avatar asked Jul 08 '17 15:07

moatPylon


1 Answers

Is it even possible to compile this in safe Rust?

No. What you are trying to do is inherently unsafe in the general case.

The collection contains a reference to a variable that will be dropped before the collection itself is dropped. This means that the destructor of the collection has access to references that are no longer valid. The destructor could choose to dereference one of those values, breaking Rust's memory safety guarantees.

note: values in a scope are dropped in the opposite order they are created

As the compiler tells you, you need to reorder your code. You didn't actually say what the limitations are for "reasons related to code organization", but the straight fix is:

fn f() {
    let a = 0;
    let b = 0;
    let mut vec = Vec::new();
    vec.push(&a);
    vec.push(&b);
}

A less obvious one is:

fn f() {
    let a;
    let b;

    let mut vec = Vec::new();
    a = 0;
    vec.push(&a);
    b = 0;
    vec.push(&b);
}

That all being said, once non-lexical lifetimes are enabled, your original code will work! The borrow checker becomes more granular about how long a value needs to live.

But wait; I just said that the collection might access invalid memory if a value inside it is dropped before the collection, and now the compiler is allowing that to happen? What gives?

This is because the standard library pulls a sneaky trick on us. Collections like Vec or HashSet guarantee that they do not access their generic parameters in the destructor. They communicate this to the compiler using the unstable #[may_dangle] feature.

See also:

  • Moved variable still borrowing after calling `drop`?
  • "cannot move out of variable because it is borrowed" when rotating variables
like image 125
Shepmaster Avatar answered Oct 23 '22 15:10

Shepmaster