Threads

Contents
thread::spawn
Join handles
Move into threads
Scoped threads note

thread::spawn

Spawn starts a closure on a new operating system thread and returns at once. The main thread keeps running beside it with no waiting. Output below may vanish when main exits first, and the next section fixes that.

use std::thread; fn main() { thread::spawn(|| { println!("hello from a thread"); }); }

The closure needs no parameters here, hence the empty bars. Spawned threads run truly in parallel on separate cores when available. Never rely on timing alone to see their results.

Join handles

Every spawn hands back a join handle that represents the running thread. Join blocks until that thread ends and returns whatever the closure produced. Unwrap surfaces a panic from the child as an error in the parent.

use std::thread; fn main() { let handle = thread::spawn(|| { 2 + 2 }); let answer = handle.join().unwrap(); println!("{}", answer); }

The handle owns the thread outcome, so dropping it without joining detaches the work. Answer holds the plain integer once join succeeds. Joining every handle keeps shutdown order predictable.

Move into threads

Threads outlive the scope that starts them, so borrowed captures are forbidden. The move keyword transfers ownership of used values into the closure. Name travels into the thread here, and the outer binding stays gone afterwards.

use std::thread; fn main() { let name = String::from("ada"); let handle = thread::spawn(move || { println!("hello, {}", name); }); handle.join().unwrap(); }

Move applies to every capture at once, not just one variable. Values that implement Copy duplicate instead of moving. Static bounds plus move together guarantee the thread never dangles.

Scoped threads note

Scoped threads lift the static restriction for threads that provably end in time. The scope waits for every child before returning, so borrowing the surroundings is safe. Nums below is only borrowed, and the scope proves it outlives the read.

use std::thread; fn main() { let nums = vec![1, 2, 3]; thread::scope(|s| { s.spawn(|| { println!("{:?}", nums); }); }); }

Scope joins all children automatically at its closing brace, even on panic paths. Borrowing avoids clones and Arcs for short parallel bursts. Prefer scoped threads whenever the work fits inside one function body.

Next: Mutex and Arc

Article author: Arthur Isaev

Related articles
Rust
Rust course: zero to hero in 50 lessons
What is Rust and where it runs
Install Rust
cargo new
Your first program
Variables
Numbers
Strings
Booleans
Tuples
Arrays
Vectors
Slices
if and else
match
Loops
Ownership
Borrowing
Lifetimes
Functions
Structs
Enums in Rust
Advanced match in Rust
Option combinators in Rust
Result and the question mark in Rust
Advanced strings in Rust
Vec methods in Rust
HashMaps in Rust
Modules in Rust
Crates and workspaces in Rust
Testing in Rust
Error handling
Traits
Generics
Trait bounds
Closures
Iterators
Smart pointers
Threads
Mutex and Arc
Files
CLI arguments
Environment variables
Diagnostics
Time
Parsing strings
TCP networking
Capstone: wordfreq CLI
Capstone: tiny HTTP server
Modern Rust tour
Hero roadmap: the whole course on one page
Install Rust on Windows 11
Install Rust on Ubuntu
Install Rust on Rocky Linux
Install Rust on macOS
Install Rust on FreeBSD
Rust in Docker
Banner image

Search this site

Channel @aofeed Chat @aofeedchat

Contacts and cooperation:
I recommend our hosting beget.com
Write to info@aredel.com if you:
1. Want to write an article for our site or translate an article into your native language.
2. Want to place thematically relevant ads on the site.
3. Ads on my site pass maximum censorship. If you see an ad block unsuitable for school-age children, shocking or misleading - please contact us by e-mail
4. Found a mistake, inaccuracy, bug, etc. on the site. ... .......
5. Articles can be shared on social media by clicking a network icon: