2

According to the documentation,

Data races cause undefined behavior and can be difficult to diagnose and fix when you’re trying to track them down at runtime; Rust prevents this problem by refusing to compile code with data races!

Why is this a problem at all unless you’re multithreading? And wouldn’t this problem persist with simultaneously accessing a variable and its one mut reference?

Herohtar
  • 5,347
  • 4
  • 31
  • 41
  • 4
    *[W]ouldn’t this problem persist with simultaneously accessing a variable and its one mut reference?* - Yes, which is why Rust doesn't let you do that, either. – trent Apr 12 '22 at 23:29
  • 1
    *Why is this a problem at all unless you’re multithreading?* - Maybe it is, maybe it isn't. [The Problem With Single-threaded Shared Mutability](https://manishearth.github.io/blog/2015/05/17/the-problem-with-shared-mutability/) explores ways this can be a problem. But there are cases where single-threaded execution makes things easier by ignoring parallelism, and Rust lets you do that, too (`Cell`, for instance). – trent Apr 12 '22 at 23:31
  • 5
    As for the title, *If two mutable references aren’t allowed in Rust simultaneously, why are they allowed at all?* - I'm not able to understand what you're asking. Are you saying there is some situation where Rust *does* allow multiple mutable references to the same value? Or are you asking why Rust even has mutability in the first place? Please indicate what kind of answer you're hoping for. – trent Apr 12 '22 at 23:34
  • You don't need multithreading to have (logical) concurrency. – HTNW Apr 12 '22 at 23:38
  • 1
    Related: [Why does Rust disallow mutable aliasing?](https://stackoverflow.com/q/49174630/3650362) and the linked [How do intertwined scopes create a "data race"?](https://stackoverflow.com/q/61851916/3650362). Possibly [Why does creating a mutable reference to a dereferenced mutable reference work?](https://stackoverflow.com/q/45095523/3650362) for the last part. – trent Apr 12 '22 at 23:42
  • @trent I was asking how if two mutable references aren’t allowed, why is one allowed in the first place because it would seem like it would have the same problem with the variable itself. I am a COMPLETE beginner coming from C++ by the way. – PunkyMunky64 Apr 12 '22 at 23:46
  • 2
    @PunkyMunky64 Then this question may be the same as yours: https://stackoverflow.com/questions/71549524/lifetime-borrow-rules/. – Chayim Friedman Apr 13 '22 at 00:05

1 Answers1

9

You seem to be confused about some things.

First, you seem to be under the impression that if you mutably borrow from a value, you can mutate the value through the mutable reference and directly using the variable that you borrowed from. This isn't true. The variable is effectively unusable until the borrow ends:

fn main() {
    let mut x = 0;
    let y = &mut x;

    // The following line causes a compile-time error.
    // error[E0506]: cannot assign to `x` because it is borrowed
    x = 1;

    *y = 2;
}

There can be at most one name through which a value can be modified at any given moment. Since x was mutably borrowed by y, the name x can't be used to modify the value until the borrow by y is released.

Why is this a problem at all unless you’re multithreading?

Because mutating state can invalidate references.

Consider the case where you have a Vec. You are not allowed to modify the Vec or any of its contents while the Vec or any of its elements are borrowed because that's unsafe. Why would it be unsafe in single-threaded code?

What happens if you have a reference to an item and that item is removed from the Vec? You have a reference to either an invalid value or a different value than you did before.

What happens if you add an element to the Vec, but its internal array is full? The Vec will allocate a new, larger array, transfer all of the elements from the smaller array into the larger array, then destroy the smaller array. All previously-existing references to elements would be invalidated by this operation.

This is why you can't mutate a Vec while you iterate it, for example:

for item in some_vec.iter() {
    some_vec.push(item.clone());
}

Vec::push() requires mutably borrowing the Vec, but iteration requires immutably borrowing the Vec. You can't do both at the same time.

Rust doesn't let you do these things because they are fundamentally unsound operations. The entire purpose of the Rust language is to catch exactly these sorts of problems at compile time.

Note that this is also unsafe in C++, for example -- adding an element to an std::vector might invalidate all existing references (in the case of a reallocation), which can cause undefined behavior when you attempt to use an invalidated reference.

The difference is that C++ will accept this dangerous code. In C++, it is the programmer's job to check for memory safety, and if you mess it up you have a potentially disastrous bug, or even a severe security vulnerability.

This is why Rust was created -- to take this critical task that humans aren't good at, and have the compiler do it for you.

cdhowie
  • 158,093
  • 24
  • 286
  • 300
  • 5
    I find [the table of C++ `std::vector` iterator invalidation](https://en.cppreference.com/w/cpp/container/vector) kind of comical, in a depressing sort of way. "Here's a list of ways to invoke UB by writing completely normal-looking, error-free code!" – trent Apr 13 '22 at 01:08
  • 5
    @trent "But I'm smart enough to use it correctly." -Uttered moments before the creation of a CVSS 10.0 vulnerability. – cdhowie Apr 13 '22 at 01:35
  • @cdhowie Memory-safety vulnerabilities are usually not 10.0 because they're hard to exploit, but it's bad enough :) – Chayim Friedman Apr 13 '22 at 02:24
  • I guess i never responded when I posted this awhile ago, but thank you, that's exactly what I needed!! – PunkyMunky64 Aug 27 '22 at 00:46