Threads com escopo
Empréstimos entre threads
Threads com escopo é uma aula grátis de Learn Rust Coding no CoddyKit. Esta é a aula 3 de 4. Você pode ler a aula completa abaixo gratuitamente — depois pratica ao vivo no navegador com um editor de código integrado e um tutor de IA 24/7. Faz parte do caminho de aprendizado de Learn Rust Coding, e seu progresso é sincronizado entre a web e o app CoddyKit. O curso de Learn Rust Coding inclui 4 aulas no total.
Partes desta aula ainda não foram traduzidas e aparecem em inglês.
The Problem with Borrowing in Threads
A normal thread::spawn closure must be 'static: it cannot borrow local variables, because the thread might outlive the function that owns them. That is why you so often see move and Arc.
Scoped threads solve this by guaranteeing every thread finishes before the scope ends, so borrowing local data becomes safe.
Why spawn Needs 'static
With thread::spawn, the spawned thread can keep running after main or any function returns. If it borrowed a local variable, that variable could be destroyed while the thread still used it. Rust forbids this at compile time, forcing you to move owned data into the closure.
use std::thread;
fn main() {
let nums = vec![1, 2, 3];
// move transfers ownership into the thread
let handle = thread::spawn(move || {
println!("in thread: {:?}", nums);
});
handle.join().unwrap();
}Introducing thread::scope
Stabilized in Rust 1.63, std::thread::scope creates a scope where threads can borrow local variables. The scope blocks until all threads inside it complete, so the borrows can never dangle.
You spawn with s.spawn(...) using the scope handle s instead of thread::spawn.
use std::thread;
fn main() {
let data = vec![10, 20, 30];
thread::scope(|s| {
s.spawn(|| {
println!("borrowed: {:?}", data);
});
});
// data is still usable here
println!("after scope: {:?}", data);
}Borrowing Without move
Inside thread::scope you can read local variables by reference without move. Multiple scoped threads can share an immutable borrow of the same data at the same time, just like normal references.
use std::thread;
fn main() {
let message = String::from("shared text");
thread::scope(|s| {
s.spawn(|| println!("thread 1 sees: {}", message));
s.spawn(|| println!("thread 2 sees: {}", message));
});
println!("main still owns: {}", message);
}Splitting Work Across a Slice
A common pattern is to split a slice and let each thread process a chunk. Scoped threads make this clean because each thread can borrow part of the original slice directly, no cloning required.
use std::thread;
fn main() {
let numbers = [1, 2, 3, 4, 5, 6];
let (left, right) = numbers.split_at(3);
thread::scope(|s| {
s.spawn(|| {
let sum: i32 = left.iter().sum();
println!("left sum: {}", sum);
});
s.spawn(|| {
let sum: i32 = right.iter().sum();
println!("right sum: {}", sum);
});
});
}Collecting Return Values
Like regular threads, s.spawn returns a ScopedJoinHandle. Call .join() to get the thread's return value. You can collect handles and join them after spawning to gather results.
use std::thread;
fn main() {
let inputs = [2, 4, 6];
let mut handles = vec![];
thread::scope(|s| {
for &x in &inputs {
handles.push(s.spawn(move || x * x));
}
let results: Vec<i32> = handles.into_iter()
.map(|h| h.join().unwrap())
.collect();
println!("{:?}", results);
});
}Mutable Borrows Need Care
Two scoped threads cannot hold mutable borrows of the same data at once; that would break Rust's aliasing rules. To mutate shared data from multiple threads you still need a Mutex, but a single thread can take a unique mutable borrow of disjoint pieces.
Below, each thread mutates a separate half of the array via split_at_mut.
use std::thread;
fn main() {
let mut data = [1, 2, 3, 4];
let (a, b) = data.split_at_mut(2);
thread::scope(|s| {
s.spawn(|| { for x in a.iter_mut() { *x *= 10; } });
s.spawn(|| { for x in b.iter_mut() { *x += 100; } });
});
println!("{:?}", data);
}Scope Joins Automatically
You do not have to call join on every scoped thread. When the scope closure returns, Rust automatically joins all not-yet-joined threads before continuing. This is why borrows are guaranteed valid for the whole thread lifetime.
use std::thread;
use std::time::Duration;
fn main() {
let label = String::from("task");
thread::scope(|s| {
s.spawn(|| {
thread::sleep(Duration::from_millis(30));
println!("{} done", label);
});
println!("spawned, scope will wait");
});
println!("all scoped threads finished");
}Combining Scope with Shared Mutation
When threads must mutate the same value, combine scoped threads with a Mutex. You skip Arc because the scope already lets threads borrow the local Mutex directly.
use std::sync::Mutex;
use std::thread;
fn main() {
let counter = Mutex::new(0);
thread::scope(|s| {
for _ in 0..5 {
s.spawn(|| {
let mut n = counter.lock().unwrap();
*n += 1;
});
}
});
println!("counter = {}", *counter.lock().unwrap());
}Scoped vs Spawned: When to Use Which
Use scoped threads when the work is bounded and finishes within a function, and you want to borrow stack data without Arc or cloning.
Use thread::spawn when a thread must outlive the current function or run for the whole program lifetime. Scoped threads cannot escape their scope.
Parallel Map Example
Putting it together: a tiny parallel map that transforms each element of a vector in its own thread while borrowing the input, then collects the results in order.
use std::thread;
fn parallel_double(items: &[i32]) -> Vec<i32> {
let mut handles = Vec::new();
let mut out = Vec::new();
thread::scope(|s| {
for &x in items {
handles.push(s.spawn(move || x * 2));
}
for h in handles {
out.push(h.join().unwrap());
}
});
out
}
fn main() {
let nums = vec![1, 2, 3, 4];
println!("{:?}", parallel_double(&nums));
}Quick Check
Test your understanding of scoped threads.
Recap
You learned about scoped threads:
thread::spawnrequires'staticclosures; scoped threads do not.thread::scopelets threads borrow local variables safely.- The scope auto-joins all threads before returning.
- Mutable sharing still needs a
Mutex, but noArcinside a scope. - Use scoped threads for bounded, function-local parallelism.
Perguntas Frequentes
A aula “Threads com escopo” é grátis?
Sim — o texto completo de “Threads com escopo” é grátis para ler aqui na web. Para praticá-la interativamente (um editor de código integrado e um tutor de IA 24/7) e desbloquear o restante do curso de Learn Rust Coding, atualize para CoddyKit PRO. O curso de Learn Rust Coding inclui 4 aulas no total.
O que vou aprender em “Threads com escopo”?
Empréstimos entre threads Você pratica Learn Rust Coding com código prático que executa diretamente no navegador, e um tutor de IA 24/7 responde suas dúvidas enquanto trabalha na aula.
Preciso ter experiência prévia para começar Learn Rust Coding?
Nenhuma experiência prévia é necessária. Learn Rust Coding no CoddyKit é estruturado para alunos iniciantes até avançados, então você pode começar aqui ou desde o início e aprender no seu ritmo. Esta é a aula 3 de 4.
Quanto tempo leva a aula “Threads com escopo”?
A maioria das aulas CoddyKit leva cerca de 5–10 minutos. Cada uma é compacta e interativa, então você faz progresso constante e retoma exatamente de onde parou entre web e app.
Posso escrever e executar código nesta aula de Learn Rust Coding?
Sim. Cada aula de Learn Rust Coding inclui um editor de código integrado, então você escreve e executa código real direto no navegador e recebe feedback de IA instantaneamente — nenhuma configuração local necessária.
Todas as aulas deste curso
- Canais mpsc
- Compartilhamento de estado com Arc/Mutex
- Threads com escopo
- Canais Crossbeam