> Why can mutable reference be only handed out once?
Here's a single-threaded program which would exhibit dangling pointers if Rust allowed handing out multiple references (mutable or otherwise) to data that's being mutated:
let mut v = Vec::new();
v.push(42);
// Address of first element: 0x6533c883fb10
println!("{:p}", &v[0]);
// Put something after v on the heap
// so it can't be grown in-place
let v2 = v.clone();
v.push(43);
v.push(44);
v.push(45);
// Exceed capacity and trigger reallocation
v.push(46);
// New address of first element: 0x6533c883fb50
println!("{:p}", &v[0]);
The analogous program in pretty much any modern language under the sun has no problem with this, in spite of multiple references being casually allowed.
To have a safe reference to the cell of a vector, we need a "locative" object for that, which keeps track of v, and the offset 0 into v.
> The analogous program in pretty much any modern language under the sun has no problem with this, in spite of multiple references being casually allowed.
And then every time the underlying data moves, the program's runtime either needs to do a dynamic lookup of all pointers to that data and then iterate over all of them to point to the new location, or otherwise you need to introduce yet another layer of indirection (or even worse, you could use linked lists). Many languages exist in domains where they don't mind paying such a runtime cost, but Rust is trying to be as fast as possible while being as memory-safe as possible.
In other words, pick your poison:
1. Allow mutable data, but do not support direct interior references.
2. Allow interior references, but do not allow mutable data.
3. Allow mutable data, but only allow indirect/dynamically adjusted references.
4. Allow both mutable data and direct interior references, force the author to manually enforce memory-safety.
5. Allow both mutable data and direct interior references, use static analysis to ensure safety by only allowing references to be held when mutation cannot invalidate them.
It certainly doesn't guarantee it, this is just what's needed to induce a relocation in this particular instance. But this makes Rust's ownership tracking even more important, because it would be trivial for this to "accidentally work" in something like C++, only for it to explode as soon as any future change either perturbs the heap or pushes enough items to the vec that a relocation is suddenly triggered.
Here's a single-threaded program which would exhibit dangling pointers if Rust allowed handing out multiple references (mutable or otherwise) to data that's being mutated: