6
votes

I'd like to better understand the semantics behind the following Rust code:

use std::thread;

fn main() {
    let immutable = "I am not mutable";
    let mut mutable = "I am mutable";

    let handle1 = thread::spawn(move || {
        println!("Thread 1 says: {}", immutable);
    });

    let handle2 = thread::spawn(move || {
        println!("Thread 2 says: {}", immutable);
    });

    let handle3 = thread::spawn(move || {
        println!("Thread 3 says: {}", mutable);
        mutable = "go away";
    });

    let handle4 = thread::spawn(move || {
        println!("Thread 4 says: {}", mutable);
    });

    handle1.join().unwrap();
    handle2.join().unwrap();
}

I do not understand why this code compiles. I have shared the variable mutable between multiple threads and even mutated it. What exactly is going on behind-the-scenes with the memory here? Are we making multiple pointers to the same string in static memory? Are we placing two different static strings in memory? The fact that I can have two threads reading from the same immutable item doesn't surprise me, but having two threads reading from a mutable variable does.

Note that even if thread 3 runs before 4, 4 does not reflect the updated string that thread 3 sets in its println! statement. Finally, since I didn't pass using &immutable, does this mean that the value is being "moved" into each thread rather than the actual memory address?

1
What you forget is that your type is &static str, here the same sniped with a simple struct, play.rust-lang.org/…. - Stargateur
@Stargateur I get your point but I don't understand how this relates to my original question in the context of &static str. I did know that &static str I just don't understand why that is able to be passed as mutable and used in multiple threads. - the_endian
Essentially, you're asking why you can use let mut other = mutable;. Keep in mind that mutable: &str, not mutable: &mut str. You never change the slice itself. Or, speaking in C terms, you have a char const * mutable and a char const * const immutable and therefore are not able to change the contents anyway. And similar to C, mutable = "go away" only changes the slice, not its contents (C lingua: you change the pointer, not the pointed value), with the same semantics as let mutable_local = mutable beforehand, so the outer mutable stays intact. - Zeta

1 Answers

8
votes

I have shared the same variable mutable between multiple threads and even mutated it.

No, you have copied a reference to the same static string into multiple threads. The reference points to a constant static string which can't be mutated. Read-only references are Copy, so you can move them into multiple closures.

What exactly is going on behind-the-scenes with the memory here?

A string slice is essentially a pointer to the beginning of a string in memory together with a length. Your variables mutable and immutable only contain these two pieces of information, and only these pieces are mutable for mutable. The actual strings the variables point to are immutable. When "moving" the variables into the closures, they are actually copied, since &str is Copy. The only information that is copied is the pointer and the length, not the actual string data. You end up with multiple pointers to the same read-only memory, which doesn't allow for any data races and conforms to Rust's memory safety rules.

Also, note that even if thread 3 runs before 4, 4 does not reflect the updated string that thread 3 sets in its println! statement.

You only modify the copy of the pointer and the length. In thread 3, mutable becomes a separate variable local to the closure, and you only modify that.

Finally, since I didn't pass using &immutable, does this mean that the value is being "moved" into each thread rather than the actual memory address?

The variable immutable has the type &'static str, so it is already a reference; &immutable would be a reference to a reference.