Multithreaded References

#1

First of all, I am new to rust, But it is my understanding that Rust
only allows the user to have a reference of any mutable variable inside of one thread. I see why Rust does this, but I suggest that instead of allowing what was stated above, to allow the user to have only one mutable reference of a variable throughout a the project, but to allow the user to have any number of non-mutable references in other threads.

Currently what can be done:

//this code is prefectly acceptable--------
fn main() {

let x = 10;

std::thread::spawn(move || {
     loop {
          println!("thread 2 x: {}", x);
     }
});

loop {
     println!("thread 1 x: {}", x)
}

}
//-----------------------
//this code is not acceptable but 
//I believe should be:
fn main() {

let mut x = 10;

std::thread::spawn(move || {
  //the mutable reference has been moved here:
   loop {
          x += 1;
          println!("thread 2: {}", x);
     }
});

//but the non-mutable reference can still be used, as it can't be modified.
loop_function(&x);

}

fn loop_function(x: &i32) {
     loop {
          println!("thread 1: {}", x);
     }
}

Although the latter does compile, x copies itself into the loop_function so it doesn’t get modified. I think that only one thread should be able to modify a certain variable, but any thread should be able to read it.

0 Likes

#2

The idea of only allowing a mutable reference if no other references (mutable or immutable) exist is a fundamental property of the safety of Rust.
Imagine you had a mutable reference to a vector as well as one or more immutable references (in the same thread or other threads doesn’t really matter for that example). Then you could use the immutable reference to iterate through the vector while you could use the mutable reference to modify the vector, which might lead to all kinds of problems.

0 Likes

#3

A few things to note here:

  • i32 is copy, so your thread spawning is copying the value in both of your examples. If you did not include the move statement, then it would take it by reference, but that is not allowed because then the closure passed to thread::spawn would no longer be 'static.

This seems to suggest that when the reference is created to x, x is copied into loop_function, but it’s not. You create a reference to x (The local variable that was copied when it was passed to the thread)

When you create a value:

let mut x = Option::<usize>::None;

x is now a value, not a reference. It just so happens to be that x is declared as mutable as a local variable. This is an unfortunate design flaw in Rust, mut x means the local value is mutable while &mut x means the value is guaranteed to not be shared with any other reference and can therefore be safely mutated.

Now, to answer your questions, commonly, reading a value while it’s being written to is undefined behaviour, which is the main thing Rust tries to circumvent, therefore having a mutable reference to a value and and read only reference is not allowed. What you are trying to achieve can be done with a

Arc<Mutex<T>>

Or a

Arc<RwLock<T>>
3 Likes

#4

Just to add to @OptimisticPeach’s great answer: your intuition might have been based on integers being modifiable in an atomic fashion. Such thing is possible in Rust but requires telling the compiler to enforce it for all architectures with Sync types with Interior Mutability such as ::std::sync::atomic::AtomicIsize (with Ordering::SeqCst when in doubt):

use ::std::{
    sync::atomic::{
        AtomicIsize,
        Ordering::SeqCst,
    },
    thread,
};

fn main ()
{
    static X: AtomicIsize = AtomicIsize::new(10);

    thread::spawn(|| {
        //the mutation happens here:
        loop {
            X.fetch_add(1, SeqCst);
            println!("thread 2: {}", X.load(SeqCst));
        }
    });

    // but the value can still be read.
    loop_function(&X);
}

fn loop_function (x: &'_ AtomicIsize)
{
     loop {
          println!("thread 1: {}", x.load(SeqCst));
     }
}
  • Playground (modified to use ::crossbeam::scope to avoid needing a static integer, and using time to avoid looping endlessly)
1 Like