let mut s = String::from("hello");
let r1 = &s; // no problem
let r2 = &s; // no problem
println!("{} and {}", r1, r2);
// variables r1 and r2 will not be used after this point
let r3 = &mut s; // no problem
println!("{}", r3);
What if println! was threaded and r1 and r2 are still used after/during the next println! statement?
Rust has special traits (Sync and Send) as a way to guarantee memory safety across threads. Read about concurrency in Rust and you'll find out about them.
Yes, imagine println! could be any function.
Rust was mentioned multi threaded per default or fearless concurrency, so I thought it would be safe to automatically make any function call threaded.
In the hypothetical where println!was threaded, then it would have to require that its inputs are 'static (i.e. not references to local data) and the code wouldn't compile — similarly to how std::thread::spawn()does require that its inputs are 'static.
Here's a case where a mutable reference is used by a thread:
use std::thread;
fn main() {
let mut a = vec![1, 2, 3];
let r = &mut a;
println!("r is {:?}", r);
thread::scope(|s| {
s.spawn(|| {
println!("in another thread");
r.push(4);
println!("r is {:?}", r);
});
});
println!("back in main thread");
r.push(5);
println!("r is {:?}", r);
println!("a is {:?}", a);
}
Vec<i32> is both Sync and Send. The thread's lambda reborrows r. Since r is a &mut, the borrow checker prevents another thread's lambda from also reborrowing it. Try to duplicate the s.spawn(...) block; the borrow checker will complain. The borrow checker also prevents the main thread from accessing it (that would be another reborrow) while the thread::scope is still active. After the thread::scope ends, it's safe for the main thread to access it again.
There's nothing multi-threaded "by default". Rust programs are thread-safe. This doesn't necessarily mean that they are multi-threaded. It only means that if you are going to create threads, then the language still ensures safety.
As already mentioned, the way it ensures thread safety is lifetime and trait bounds. Unbounded threads require 'static values, scoped threads require that the spawned function borrows for 'scope at most, and threading in general requires Send and/or Sync types.
But since printing isn't threaded, there's absolutely no need for any such bound, it would only be overly restrictive.
I'm increasingly confused by what your actual question or problem is. Threading has nothing to do with exclusivity of mutable borrows. Mutable aliasing causes memory-unsafety in single-threaded code too. It's not because "Rust is not designed" with threads in mind or anything like that. Shared mutability is disallowed because it causes bugs, period. Disallowing it is not a bug, it's a feature.
Well, I thought I couldn't borrow immutable after borrowing mutable because of "multi threaded by default" behavior, but it seems to be another reason why this isn't possible.
People often call regular borrows shared borrows and mutable borrows exclusive borrows. It helps emphasize the exclusivety property, which is a major foundation of Rust's safety.