Mutable owner and mutable borrow

Going through the Rust book and something didn't make sense to me

How can you have a mutable owned value, and then pass a (single) mutable reference?

Doesn't this create a data race?

Example:


fn main()
{
    let mut my_string = String::from("hello");

    //If this were async, wouldn't we have a data race?
    //I don't know enough Rust yet to actually make it async though - sorry!
    append_string(&mut my_string, " world");
    
    my_string.push('!');
    
    println!("{}", my_string);
    
}

fn append_string(target: &mut String, suffix: &str) -> () {
    target.push_str(suffix);
}

(Playground)

1 Like

If it were async, this wouldn't compile. Sending things across threads is only allowed for things which are Send. Mutable borrowed things (&mut Xxxx) are not Send, and nothing containing them can be Send either.

This works precisely because append_string does not modify your my_string variable, and since it isn't async nothing else can modify your variable while it runs either.

For more info on this, see Fearless Concurrency - The Rust Programming Language.

1 Like

ahhh cool. Thanks :slight_smile:

2 Likes

If T: Send so is &mut T; the rationale is you’re holding a unique alias to that location, so you’re a temporary owner.

Sorry @vitalyd- can you please break that down a bit more? I didn't understand (yet)..

This is not entirely about thread safety (you could have an async function that is executed in the same thread with a delay). The code is safe because while append_string() receives a &mut my_string, it can't just store it for an arbitrarily long time without making an owning copy. Any structure that holds a reference will have a lifetime generic parameter, and the borrow checker will make sure that you can't use that struct if the reference can't be proven valid at the time of use.

4 Likes

Sorry @dakom, I should've elaborated but fortunately, @Riateche explained it :slight_smile:. The key aspect, as they say, is the ability to retain (or not) the borrow across the call. Vast majority of code that executes something on a different thread will require a 'static bound (in addition to Send) on the type that's being moved to the other thread. The 'static bound prevents the issue you were alluding to. And, you can't mutably borrow a static binding without using unsafe code, so that precludes trying to sneak data around via static values.

I mostly wanted to point out that &mut T can be sent across threads (if T: Send), which was mentioned earlier in this thread as being impossible. This can be very useful when using crates such as crossbeam, where it has scoped execution - you submit a closure to a background thread, but its API blocks until that closure completes. Given that, it allows passing (mutable) references to stack values, which can be handy at times. Here is your append_string example using crossbeam.

2 Likes

Is it correct then that crossbeam does not have a static bound - but rather some other lifetime parameter? (I haven't gotten to lifetimes in the book yet - only vaguely aware that they sortof specify and associate scopes explicitly to the compiler)

Right, it doesn’t require 'static - crossbeam::thread - Rust talks a bit about this.