14

Editor's note: This code example is from a version of Rust prior to 1.0 and is not syntactically valid Rust 1.0 code.

Is it possible to share a mutable variable between multiple threads in Rust? Given the following:

fn main() {

    let mut msg = "Hi";
    // ...
    msg = "Hello, World!";

    do spawn {
        println(msg);
    }

    do spawn {
        println(msg);
    }

}

I get this error:

error

The variable just needs to be readonly to the spawned threads. The variable has to be mutable though, because what I'm really trying to do is share a HashMap between multiple threads. As far as I know there is no way to populate a HashMap unless it's mutable. Even if there is a way to do that though, I'm still interested in knowing how to accomplish something like this in general.

Thank you!

0

4 Answers 4

9

This restriction is slated to be removed in a future version of the language. That said, you can fix this problem with let msg = msg; before the first do spawn. That will move the value msg into an immutable location, effectively changing its mutability.

Sign up to request clarification or add additional context in comments.

2 Comments

When you say "this restriction is slated to be removed", what exactly is changing? Copy-on-capture will become the default? Or, you'll be able to capture a mutable variable as long as your lambda is created after the last assignment to it?
@JasonOrendorff The former, it seems; Closures: Anonymous Functions that Capture Their Environment: move occurs because value has type String, which does not implement the Copy trait
6

The "in general" answer (about keeping a hash table mutable while it's shared) is that it's not directly possible; Rust was specifically designed to forbid the sharing of mutable state in order to avoid certain kinds of bugs and to protect certain kinds of safety (and for other reasons).

However, it is possible to create something much like a shared mutable hash table. Imagine that a single task has a mutable hash table. You can write code so that other tasks can send it messages saying, essentially "Update the hash table so that 5 maps to 18" or "Tell me what 7 maps to".

What does this indirection accomplish? First, by copying values into messages, it's not possible for another task to trample all over memory that you're in the middle of reading (a big safety problem), and second, by writing the code indirectly, it's more clear to the programmer what is and is not an atomic operation; the programmer will know not to expect that two consecutive messages about hash table contents aren't necessarily about the same version of the hash table, an assumption that is perfectly valid (at least in Rust, thanks to some other restrictions) about two consecutive reads from an unshared, mutable, hash table.

3 Comments

That certainly sounds like a promising approach. Is there anything specific in the standard library that would help me accomplish such a thing?
Not that I know of, sorry! The standard library is still growing; you might want to consider adding it yourself.
If I figure out a way to do it I'll be sure to make it available on github :)
3

If your variable can be atomic, you can use an Arc for reference counting and the atomic type. An Arc is a reference counter which acts a lot like the garbage collector in languages that have those:

use std::sync::atomic;
use std::sync::Arc;

fn main() {
    let arc_num = Arc::new(atomic::AtomicU32::new(1337));
    let arc_num_clone = Arc::clone(&arc_num);

    let _get_borrowed_clone = move || {
        return arc_num_clone.load(atomic::Ordering::Relaxed);
    };

    arc_num.store(4242, atomic::Ordering::Relaxed)
}

playground

Likely, you can't use an atomic type, in which case you can use Arc and Mutex:

use std::sync::{Arc, Mutex};

fn main() {
    let arc_num = Arc::new(Mutex::new(1337));
    let arc_num_clone = Arc::clone(&arc_num);

    let _get_borrowed_clone = move || {
        let out = *arc_num_clone.lock().unwrap();
        return out;
    };

    *arc_num.lock().unwrap() = 4242;
}

Read more about how this code works

If you want to know more about the difference between these two approaches, read Is there any difference between "mutex" and "atomic operation"?

Comments

2

In the case of sharing the string "Hello, World!", you'd just need to move the variable back into an immutable location (e.g., let mut msg = .....; let msg = msg;).

Probably you also want to avoid making a separate copy of the hashmap for each thread that's receiving it, though, in which case you'll want to also put it inside an std::sync::Arc (atomically reference counted wrapper).

2 Comments

So when doing let msg = msg; Rust is making a immutable copy of msg for every spawned thread?
@EvanByrne in that case Rust is shadowing a previous mutable reference with a new, immutable reference. This immutable reference can then be passed to the threads. At least this is how I understand this behaviour.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.