Who is the owner of a reference to a vector?

The following rust code compiles and runs.

fn main() {
    let a = &vec![1, 2, 3];
    println!("{a:?}");
}
[1, 2, 3]

What I wonder now is the following. Who is the owner of the Vec<i32>? a only holds a reference to it. So who is responsible for dropping the vector? The rust book explains that there can never be more than one owner of the data, but can there be 0? As far as I understand it, the owner is always a variable, but maybe that's a misunderstanding and the owner of the data is the Vec<i32> itself.

2 Likes

The owner is main, because you are creating the vector within its scope. This means that the vector will be dropped at the end of main's function.

Rust has a special case that converts this:

let var_name = &expression;

to this:

let var_name_owner = expression;
let var_name = &var_name_owner;
16 Likes

Ok, the last two answers seem to be somewhat conflicting. Can someone clarify?

My answer is correct.

1 Like

You can compare to this code, which does not hit the special case:

fn returns_argument(arg: &Vec<i32>) -> &Vec<i32> {
    arg
}

fn main() {
    let a = returns_argument(&vec![1, 2, 3]);
    println!("{a:?}");
}
   Compiling playground v0.0.1 (/playground)
error[E0716]: temporary value dropped while borrowed
 --> src/main.rs:7:31
  |
7 |     let a = returns_argument(&vec![1, 2, 3]);
  |                               ^^^^^^^^^^^^^ - temporary value is freed at the end of this statement
  |                               |
  |                               creates a temporary which is freed while still in use
8 |     println!("{a:?}");
  |                - borrow later used here
  |
  = note: consider using a `let` binding to create a longer lived value
  = note: this error originates in the macro `vec` (in Nightly builds, run with -Z macro-backtrace for more info)

For more information about this error, try `rustc --explain E0716`.
error: could not compile `playground` due to previous error
4 Likes

Ah thanks very much. On the same note. Why is there a difference between mutability of a variable and mutability of a reference, i.e.

let a = &mut vec![1, 2, 3];

vs

let mut a = vec![1, 2, 3];

but there does not seem to be a concept of mutable data like

let a = mut vec![1, 2, 3];

Such that you coud do

a.push(2);

but not

a = vec![4, 5, 6]

Which is possible in the reference scenario.

The reference describes the special-cased behavior here:

https://doc.rust-lang.org/stable/reference/destructors.html#temporary-lifetime-extension

3 Likes

Immutable and mutable references are different types and have various important differences. On the other hand, when something is owned, it is in some sense always mutable. The mut keyword on a variable is not part of the type system, and just controls whether you are allowed to create mutable references to the value. In reality its just a lint that emits an error rather than a warning.

6 Likes

Thanks so much :grin:

Experimenting a bit more with what you said:

fn main() {
    println!("{:?}", &vec![1, 2, 3]);
    
    let a = my_func(&vec![4, 5, 6]);
    println!("{a:?}");
    
    let b = returns_argument(&vec![7, 8, 9]);
    println!("{b:?}");
    
    prints_argument(&vec![10, 11, 12]);
}

fn my_func(arg: &Vec<i32>) {}

fn prints_argument(arg: &Vec<i32>) {
    println!("{arg:?}");
}

fn returns_argument(arg: &Vec<i32>) -> &Vec<i32> {
    arg
}
$ cargo build
   Compiling playground v0.1.0 (/home/explorer/playground)
warning: unused variable: `arg`
  --> src/main.rs:18:12
   |
18 | fn my_func(arg: &Vec<i32>) {}
   |            ^^^ help: if this is intentional, prefix it with an underscore: `_arg`
   |
   = note: `#[warn(unused_variables)]` on by default

error[E0716]: temporary value dropped while borrowed
  --> src/main.rs:12:31
   |
12 |     let b = returns_argument(&vec![7, 8, 9]);
   |                               ^^^^^^^^^^^^^ - temporary value is freed at the end of this statement
   |                               |
   |                               creates a temporary which is freed while still in use
13 |     println!("{b:?}");
   |                - borrow later used here
   |
   = note: consider using a `let` binding to create a longer lived value
   = note: this error originates in the macro `vec` (in Nightly builds, run with -Z macro-backtrace for more info)

For more information about this error, try `rustc --explain E0716`.
warning: `playground` (bin "playground") generated 1 warning
error: could not compile `playground` due to previous error; 1 warning emitted

source: Rust Explorer

It seems the error could be a bit more clear. The problem seems to be the binding of the result of the expression on the right to the variable b originating from a temporary value. Because it seems there is no problem with using the temporary value inside the functions my_func and prints_argument or the plain println! macro.

1 Like

Yeah, I suspect it's one of those things where the error message makes perfect sense when you understand the problem, but it's complete gibberish if you haven't been exposed to it before.

The compiler also says "note: consider using a let binding to create a longer lived value", which should hopefully be enough to nudge newbies towards building the right intuition (assuming they read the full error message).

And that's exactly why rustc --explain E0716 exists and is pointed out by the error message.

2 Likes

I beg to differ. I don't really see how it could be any clearer (let alone how it deserves to be called "complete gibberish"): the value being borrowed and dropped is the exact problem. There's nothing more to it: this is the very definition of a dangling pointer, which is directly caused by the code, and the only way to change this error message I can imagine would be to change some of the words to synonyms, but that wouldn't help.

2 Likes

Yeah, but in my opinion it is not very precise about when it is beeing dropped. From the error I would think it is beiing dropped before it is beeing passed to the function because I read from inside to outside. I.e. first allocation, then vector, then reference, then funtion call with reference as arg. But apparently it is no problem to use the reference inside the function but only once you assign the returned reference to a variable, i.e. the drop must happen after function evaluation.

Yeah, I hear you and that was actually my first reaction, too.

However, the two of us have both been helping people with Rust for years, and as such we are succeptible to a cognitive bias called the curse of knowledge:

For example, the curse of knowledge can mean that an expert in some field might struggle to teach beginners, because the expert intuitively assumes that things that are obvious to them are also obvious to the beginners, even though that’s not the case.

3 Likes

I can assure you it's not like that at all. I never had any trouble understanding these explanations from compiler and even helping others immediately after I just finished reading Rust book.

In some sense it is curse of knowledge , but 10 times out of 9 it's with newbies, not veterans.

Someone observes the fact that rust compiler speaks in English and immediately assumes compiler is just like me and you only smarter. And it applies common sense to the program thus you can emote with it.

Nothing can be further from the truth. Compiler is extremely dumb and it just applies bunch of simple rules to your program. Yes, it's tireless, it doesn't ask for wage raise, but it's as dumb as they come and it doesn't even have any common sense.

If newbie can accept that and would stop trying to find a common sense in a compiler — s/he can easily understand error message and other related things. If newbie tries to apply things which come from real world observations and which compiler doesn't have any idea about — frustration may continue forever.

You just have to accept that while compiler is diligent, accurate, and tireless… it's also infinitely dumber than any normal human and you should assume that it understands anything at all in your program. It couldn't do that. It just have to organs suitable for understanding.

2 Likes

Note that the error message does point out the following w.r.t. the question of when it is being dropped:

12 |     let b = returns_argument(&vec![7, 8, 9]);
   |                                             - temporary value is freed at the end of this statement

Of course, in order to understand this properly, it helps to know what a statement is.

The whole line

let b = returns_argument(&vec![7, 8, 9]);

is a single statement, in particular a let-statement. (One can argue about whether or not the semicolon is part of the statement or not.)

The error message seems to be designed to point only to the semicolon that ends the statement instead of the whole thing, presumably because it’s more readable this way, and it’s talking about the end of the statement anyways.


In terms of improving the error message there are two details that could be changed:

  • it currently says “freed” instead of “dropped”, which is inconsistent wording, since the whole error message is “temporary value dropped while borrowed”
  • it says “creates a temporary which is freed while still in use” while pointing to the place where
    • the temporary is created, but
    • the temporary is not freed in this location, and
    • the temporary is not “still in use (after being freed)” in this location.

Both of these points can lead to confusion as to when the temporary is dropped. The first point because the connection between “temporary value dropped while borrowed” and “temporary value is freed at the end of this statement” is not 100% clear (IMO, it’s still pretty clear, but I see room for improvement); the second point is where the confusion about why using it in a function call, but not in the println later might come from, because “creates a temporary which is freed …” might be interpreted that the pointed-to code location is both where the temporary is created and where it’s dropped. (This interpretation doesn’t make too much sense if you thing about it more, and also it’s unlikely that someone familiar with how temporaries work would draw this conclusion, but that’s presumably where the abovementioned “curse of knowledge” could come into play.)


In terms of concrete, actionable improvements, my ideas would be:

  • use dropped instead of freed
  • use definite articles, to make clearer that we’re talking about the same temporary, and the same dropping and the same “still in use”/“later used here”; also referencing “the temporary” in the “borrow later used here” seems useful

E.g.

error[E0716]: temporary value dropped while borrowed
  --> src/main.rs:12:31
   |
12 |     let b = returns_argument(&vec![7, 8, 9]);
   |                               ^^^^^^^^^^^^^ - the temporary value is dropped at the end of this statement
   |                               |
   |                               creates a temporary which is dropped while still in use
13 |     println!("{b:?}");
   |                - borrow of the temporary later used here
   |

The last remaining problem is the out-of-order of the messages (the main “creates a temporary …” message should arguably come first), but that’s a common thing in rust error messages (and also, the color-coding helps if you read this message in the terminal: the main “creates a temporary …” message is red).

5 Likes