Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why Rust prevents from multiple mutable references?

Like in the topic, why Rust prevents from multiple mutable references? I have read chapter in rust-book, and I understand that when we have multi-threaded code we are secured from data races but let's look at this code:

fn main() {
    let mut x1 = String::from("hello");
    let r1 = &mut x1;
    let r2 = &mut x1;

    r1.insert(0, 'w');

}

This code is not running simultaneously so there is no possibility for data races. What is more when I am creating new thread and I want to use variable from parent thread in a new thread I have to move it so only new thread is an owner of the parent variable.

The only reason I can see is that programmer can lose himself in his code when it is growing up. We have multiple places in which one piece of data can be modified and even the code is not running parallel we can get some bugs.

like image 269
mikeProgrammer Avatar asked Oct 13 '19 15:10

mikeProgrammer


People also ask

Why does Rust only allow one mutable reference?

Preventing two mutable references makes it possible to keep invariants on types easily and let the compiler enforce that the invariant are not violated. This is unsafe because you cannot mutate a vector while you are iterating it.

What is a mutable reference Rust?

Back to Rust. A mutable reference is a borrow to any type mut T , allowing mutation of T through that reference. The below code illustrates the example of a mutable variable and then mutating its value through a mutable reference ref_i . fn main() {

What is borrowing Rust?

Rust supports a concept, borrowing, where the ownership of a value is transferred temporarily to an entity and then returned to the original owner entity.


2 Answers

The fact that Rust prevent two mutable references at the same time to prevent data races is a common misconception. This is only one of the reasons. Preventing two mutable references makes it possible to keep invariants on types easily and let the compiler enforce that the invariant are not violated.

Take this piece of C++ code for an example:

#include <vector>

int main() {
    std::vector<int> foo = { 1, 2, 3 };
    
    for (auto& e: foo) {
        if (e % 2 == 0) {
            foo.push_back(e+1);
        }
    }

    return 0;
}

This is unsafe because you cannot mutate a vector while you are iterating it. Mutating the vector might reallocate its internal buffer, which invalidates all references. In C++, this is a UB. In Python, Java or C# (and probably most other languages), you would get a runtime exception.

Rust however, prevents this kind of issues at compile time:

fn main() {
    let mut foo = vec![1, 2, 3];
    
    for e in foo {
        if e % 2 == 0 {
            foo.push(e+1);
        }
    }
}

gives an error:

error[E0382]: borrow of moved value: `foo`
 --> src/main.rs:6:13
  |
2 |     let mut foo = vec![1, 2, 3];
  |         ------- move occurs because `foo` has type `std::vec::Vec<i32>`, which does not implement the `Copy` trait
3 |     
4 |     for e in foo {
  |              ---
  |              |
  |              value moved here
  |              help: consider borrowing to avoid moving into the for loop: `&foo`
5 |         if e % 2 == 0 {
6 |             foo.push(e+1);
  |             ^^^ value borrowed here after move
like image 110
mcarton Avatar answered Oct 05 '22 00:10

mcarton


The big benefit of this restriction is that rust can prevent data races at compile time. A data race occurs if we have two pointers pointing to the same piece of data and one of those pointers is used to write to the data and there is no mechanism to synchronize data access between those pointers. In that situation, u could imagine one pointer will read the data and in the middle, another pointer modifying the data. in that case, we are gonna get corrupted data back. to fix this error you can switch these references back to be immutable references.

like image 25
Yilmaz Avatar answered Oct 05 '22 01:10

Yilmaz