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

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
nodakai
  • 7,773
  • 3
  • 30
  • 60
  • The compiler message about dropping has nothing to do with your call to `drop()`, only the expiration of variable lifetimes at the end of the block. – ljedrz Apr 16 '17 at 06:00

2 Answers2

9

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

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
Lukas Kalbertodt
  • 79,749
  • 26
  • 255
  • 305
  • You beat me to it by 2 minutes! I thought this was my chance to answer a Rust question while Shepmaster seemed to be taking a break :) – Peter Hall Apr 15 '17 at 18:23
  • @PeterHall Oh no, I'm sorry :( You could join us [in chat](http://chat.stackoverflow.com/rooms/62927/rust) if you like ^_^ – Lukas Kalbertodt Apr 15 '17 at 18:28
  • "So: the compiler doesn't know x is dropped, it just knows x is moved." I'm intrigued, are you trying to say *anything* here? – nodakai Apr 15 '17 at 21:36
  • @nodakai Yes, "moving" != "dropping". For example, a normal vector can be moved without being dropped. In fact by moving it can live longer. An extreme example of that is `mem::forget()`: the argument is moved into that function but is never ever dropped. Does this help or is anything still unclear? – Lukas Kalbertodt Apr 15 '17 at 21:44
  • `forget()` (to be more precise `drop_in_place()`) *is* a compiler intrinsic which is exceptional to Rust's linear type system. I don't think it's fair to refer to it here. Otherwise any functions which take a parameter via move and doesn't return anything (for example) should be understood as data "sink" which drop the moved data. – nodakai Apr 15 '17 at 21:55
  • @nodakai You're right, `forget()` is not an optimal example here. But functions taking types by value do *not* necessarily drop them, even if they don't return anything. They could store the value in a static ([example](http://play.integer32.com/?gist=090f56867efab87f87d8dea9e1f5a671&version=undefined)) or send it to another thread. When you *only* move a value into a function, then yes, the value is dropped when the function exits. But the function can move the value somewhere else, e.g. another thread. And thta something else can live longer than the function. – Lukas Kalbertodt Apr 15 '17 at 22:07
  • Again, what are you trying to claim by throwing [a bunch of `unsafe` code](https://github.com/rust-lang-nursery/lazy-static.rs/blob/master/src/lib.rs#L131) to me? Anything can happen with `unsafe` (granted, it won't immediately turn off all the type checks but it *does* open up a way to circumvent them, like your example did under the hood.) But +1 to your mention to multithreading, I didn't specifically exclude [`Send`](https://github.com/rust-lang/rfcs/blob/master/text/0458-send-improvements.md) types which `Vec<&'a i32>` in my original question *happened* not to implement. – nodakai Apr 15 '17 at 23:27
  • 4
    @nodakai I'm not quite sure what you're trying to achieve with this discussion :/ As far as I can see, your question is answered. Your comments look like you want to argue against me instead of trying to understand. My remark that `drop()` isn't special is certainly true, because even the docs clearly state that. Whether or not a function always has to drop or return an owned value when not using unsafe code, that I think is an interesting question! But we shouldn't explore it here in the comments, but by asking a new question here on SO. And btw: `Vec<&i32>` does implement `Send`. – Lukas Kalbertodt Apr 16 '17 at 06:07
7

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

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
Peter Hall
  • 53,120
  • 14
  • 139
  • 204