Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Moved variable still borrowing after calling `drop`?

Tags:

rust

lifetime

fn main() {
    let mut x: Vec<&i32> = vec![];
    let a = 1;
    x.push(&a);
    drop(x);
    // x.len(); // error[E0382]: use of moved value: `x`
}  // `a` dropped here while still borrowed

The compiler knows drop() drops x (as evident from the error in the commented-out code) but still thinks the variable is borrowing from a! This is unfair!

Should this be considered as one of numerous dupes of rust-lang/rust#6393 (which is now tracked by rust-lang/rfcs#811?) But the discussion there seems to be centered on making &mut self and &self coexist in a single block.

like image 875
nodakai Avatar asked Apr 15 '17 17:04

nodakai


2 Answers

I can't give you a definite answer, but I'll try to explain a few things here. Let's start with clarifying something:

The compiler knows drop() drops x

This is not true. While there are a few "magic" things in the standard library that the compiler knows about, drop() is not such a lang item. In fact, you could implement drop() yourself and it's actually the easiest thing to do:

fn drop<T>(_: T) {}

The function just takes something by value (thus, it's moved into drop()) and since nothing happens inside of drop(), this value is dropped at the end of the scope, like in any other function. So: the compiler doesn't know x is dropped, it just knows x is moved.


As you might have noticed, the compiler error stays the same regardless of whether or not we add the drop() call. Right now, the compiler will only look at the scope of a variable when it comes to references. From Niko Matsakis' intro to NLL:

The way that the compiler currently works, assigning a reference into a variable means that its lifetime must be as large as the entire scope of that variable.

And in a later blog post of his:

In particular, today, once a lifetime must extend beyond the boundaries of a single statement [...], it must extend all the way till the end of the enclosing block.

This is exactly what happens here, so yes, your problem has to do with all this "lexical borrowing" stuff. From the current compilers perspective, the lifetime of the expression &a needs to be at least as large as the scope of x. But this doesn't work, since the reference would outlive a, since the scope of x is larger than the scope of a as pointed out by the compiler:

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

And I guess you already know all that, but you can fix your example by swapping the lines let mut x ...; and let a ...;.


I'm not sure whether or not this exact problem would be solved by any of the currently proposed solutions. But I hope that we will see soon enough, as all of this is being addressed as part of the Rust 2017 roadmap. A good place to read up on the updates is here (which also contains links to the five relevant blog posts of Niko).

like image 176
Lukas Kalbertodt Avatar answered Oct 22 '22 21:10

Lukas Kalbertodt


The compiler knows drop() drops x (as evident from the error in the commented-out code)

The Rust compiler doesn't know anything about drop and what it does. It's just a library function, which could do anything it likes with the value since it now owns it.

The definition of drop, as the documentation points out, is literally just:

fn drop<T>(_x: T) { }

It works because it the argument is moved into the function, and is therefore automatically dropped by the compiler when the function finishes.

If you create your own function, you will get exactly the same error message:

fn my_drop<T>(_x: T) { }

fn main() {
    let mut x: Vec<&i32> = vec![];
    let a = 1;
    x.push(&a);
    my_drop(x);
    x.len();
}

This is exactly what is meant in the documentation when it says drop "isn't magic".

like image 26
Peter Hall Avatar answered Oct 22 '22 22:10

Peter Hall