Using Threads to Run Code Simultaneously
Rust uses a 1:1 threading model where each language thread maps to an OS thread. Thread creation uses thread::spawn
with closures containing the code to execute.
Creating Threads with spawn
use std::thread; use std::time::Duration; fn main() { thread::spawn(|| { for i in 1..10 { println!("hi number {i} from the spawned thread!"); thread::sleep(Duration::from_millis(1)); } }); for i in 1..5 { println!("hi number {i} from the main thread!"); thread::sleep(Duration::from_millis(1)); } }
Key behaviors:
- Main thread completion terminates all spawned threads
- Thread execution order is non-deterministic
thread::sleep
yields execution to other threads
Waiting for Thread Completion with join
thread::spawn
returns a JoinHandle<T>
that provides a join
method to wait for thread completion:
use std::thread; use std::time::Duration; fn main() { let handle = thread::spawn(|| { for i in 1..10 { println!("hi number {i} from the spawned thread!"); thread::sleep(Duration::from_millis(1)); } }); for i in 1..5 { println!("hi number {i} from the main thread!"); thread::sleep(Duration::from_millis(1)); } handle.join().unwrap(); }
Calling join
blocks the current thread until the target thread terminates. Placement of join
calls affects concurrency behavior—early joins eliminate parallelism.
Using move
Closures for Data Transfer
To transfer ownership of variables from one thread to another, use move
closures:
use std::thread;
fn main() {
let v = vec![1, 2, 3];
let handle = thread::spawn(|| {
println!("Here's a vector: {v:?}");
});
handle.join().unwrap();
}
This fails because Rust cannot guarantee the lifetime of borrowed data across thread boundaries:
$ cargo run
Compiling threads v0.1.0 (file:///projects/threads)
error[E0373]: closure may outlive the current function, but it borrows `v`, which is owned by the current function
--> src/main.rs:6:32
|
6 | let handle = thread::spawn(|| {
| ^^ may outlive borrowed value `v`
7 | println!("Here's a vector: {v:?}");
| - `v` is borrowed here
|
note: function requires argument type to outlive `'static`
--> src/main.rs:6:18
|
6 | let handle = thread::spawn(|| {
| __________________^
7 | | println!("Here's a vector: {v:?}");
8 | | });
| |______^
help: to force the closure to take ownership of `v` (and any other referenced variables), use the `move` keyword
|
6 | let handle = thread::spawn(move || {
| ++++
For more information about this error, try `rustc --explain E0373`.
error: could not compile `threads` (bin "threads") due to 1 previous error
The move
keyword forces the closure to take ownership:
use std::thread; fn main() { let v = vec![1, 2, 3]; let handle = thread::spawn(move || { println!("Here's a vector: {v:?}"); }); handle.join().unwrap(); }
This transfers ownership to the spawned thread, preventing use-after-free errors and ensuring thread safety through compile-time checks.