-1

I understand why Rust doesn't allow us to do the following:

let v1 = vec![1,2,3];
let v2 = v1;
println!("Print here {}", v1[0])

But now, let's look at this, which is allowed and correct.

let v1 = vec![1,2,3];
let v2 = &v1; // borrowing here
println!("Print here {}", v1[0])

Now, I am really curious why 2nd one is allowed. For the first example, on the stack, some information gets stored(pointer of the memory address of the actual vector data, length of the vector). let v2 = v1 causes pointer and length of data to be copied from that part of the stack and gets stored on the stack again, but now, for v2 variable. Since changing v1 causes vector data to be changed + the part of the stack where v1's information was stored, rust doesn't allow this, because v2 now becomes unpredictable as it still stores the old length so stack and heap doesn't agree.

For the 2nd example, I am wondering what let v2 = &v1 does exactly so that this is allowed? I'd appreciate the explanation in the same words as I described the first case above.

Nika Kurashvili
  • 6,006
  • 8
  • 57
  • 123
  • 1
    The question is very unclear, because it keeps referring to the "stacks of vectors" (as @mkrieger1 points out). But besides that: Are you aware that Rust allows multiple *non-mutable* borrows of the same data? If not, you should probably read up on the fundamentals of Rust's ownership model. – gspr Feb 23 '22 at 10:18
  • I am referring to the actual stack in memory. I don't know what exactly is unclear. – Nika Kurashvili Feb 23 '22 at 10:19
  • 2
    The stack is not a property of the variables `v1` and `v2`. So it's unclear why you refer to "`v2`'s stack", for example. – mkrieger1 Feb 23 '22 at 10:20
  • What I mean is that `v2` variable and `v1` variable gets stored on the stack. Sorry for the confusion. Does the question make sense now ? – Nika Kurashvili Feb 23 '22 at 10:21
  • 1
    "whatever v1 had on the stack gets copied to v2's stack" still doesn't make sense, because there are no separate stacks between which data gets copied. – mkrieger1 Feb 23 '22 at 10:22
  • Jjust editted the question. – Nika Kurashvili Feb 23 '22 at 10:26
  • What borrow checker ensures is that at any given time of execution if a variable is borrowed mutably, no other borrow can occur. This is unrelated to any stack/heap, etc. – gstukelj Feb 23 '22 at 10:28
  • I didn't borrow mutably. look at the example – Nika Kurashvili Feb 23 '22 at 10:31
  • You talk about "changing `v1`". You can't change `v1` while you have a borrow to it (in the form of `v2`). That's a central idea in Rust's borrow system. Since that's not allowed, what remains of the question? – gspr Feb 23 '22 at 10:31
  • And that's what I want to understand why. how it's presented in stack/heap. in my first example, I explained how it works. but with borrows, I don't get how it's represented in stack/heap – Nika Kurashvili Feb 23 '22 at 10:33
  • Since `v2` is never used after it was declared and assigned, it is dropped immediately before the `println!` call. This is all explained in the link. – E_net4 Feb 23 '22 at 10:43
  • 4
    There's no mutation in this code, so this would have compiled in any version of Rust even before non-lexical lifetimes. – trent Feb 23 '22 at 11:43
  • @NikaKurashvili: How is *what* represented on the stack/heap? – gspr Feb 23 '22 at 11:57

1 Answers1

1

When reasoning about what Rust allows or does not allow, in term of ownership, it's not useful to think about what happens exactly in memory, because that's not how Rust thinks about it. It has a more high-level approach.

The fundamental issue with the first code is that you have emptied the variable v1 from having any value, when assigning its value to v2. That is, each value (part of memory) in Rust must have an owner (that is, a variable that has a lifetime, and which is responsible for that value), and only one owner.

When you move v1's value to v2, nothing happens in memory (it' most likely a NOP with sufficient optimization levels), but in the Great Scheme of Rust, the value's owner has changed. But then, to ensure there is only one owner, Rust makes it like v1's value moved. It's a way to tell you: it's just like there if there was nothing any more at the address of this variable, stop using it until you have put something else yourself, because I won't allow you to access what is really there until then. And since v1 was declared non-mutable anyway, this variable is pretty much useless from now on.

In the second case, the situation is completely different. The ownership of the underlying value is still v1's. v2 has a different type of access to the value: it is not responsible for it, has no write-access, and must give it back before the value is freed from memory (that is, before v1 is droped). However, for the lifetime of v2 (that is, for as much time as v2 could be used somewhere), the value is "locked" (this has nothing to do with threading locks), in the sense that you are not allowed to write to it, to share it with write-access or to give its ownership to somebody else. If you wanted to share the data with write-access, you don't want to give the ownership (presumably because you want to reuse the variable after the borrow has ended, and getting the ownership back is verbose), then you can give a mutable borrow. These are exclusive (meaning at most one other person can have a mutable borrow) and prevent the actual owner from reading or writing while the borrow is on-going.

It would be achieved like this:

let mut owner = vec![1, 2, 3];
let borrower = &mut owner;
println!("Print here {}", owner[0]);

However, if you compiled this code, you'll see it works. Strange, isn't it? I just said even the owner has no read-access to the value... The key is that the lifetime of the borrow ends just before the owner tries to read-access it, that is, Rust understands that, when trying to access the Vec, borrower will never use its borrow any more, so it can be dropped just before the access. The following code will not compile, because in this case it cannot drop borrower before owner tried to access the borrowed value.

let mut owner = vec![1, 2, 3];
let borrower = &mut owner;
println!("Print here: {}", owner[0]);
println!("Print there: {}", borrower[0]);
halfer
  • 19,824
  • 17
  • 99
  • 186
jthulhu
  • 7,223
  • 2
  • 16
  • 33