Hilos con ámbito
Prestar entre hilos
Hilos con ámbito es una lección gratuita de Learn Rust Coding en CoddyKit. Esta es la lección 3 de 4. Puedes leer la lección completa abajo gratuitamente — luego la practicas en el navegador con un editor de código integrado y un tutor de IA 24/7. Forma parte de la ruta de aprendizaje de Learn Rust Coding, y tu progreso se sincroniza en la web y la app de CoddyKit. El curso de Learn Rust Coding incluye 4 lecciones en total.
Partes de esta lección aún no han sido traducidas y se muestran en 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.
Preguntas frecuentes
¿La lección «Hilos con ámbito» es gratis?
Sí — el texto completo de «Hilos con ámbito» es gratis para leer aquí en la web. Para practicarla de forma interactiva (editor de código integrado y tutor de IA 24/7) y desbloquear el resto del curso de Learn Rust Coding, actualiza a CoddyKit PRO. El curso de Learn Rust Coding incluye 4 lecciones en total.
¿Qué aprenderé en «Hilos con ámbito»?
Prestar entre hilos Practicas Learn Rust Coding con código real que ejecutas directamente en el navegador, y un tutor de IA 24/7 responde tus preguntas mientras trabajas en la lección.
¿Necesito experiencia previa para empezar Learn Rust Coding?
No se requiere experiencia previa. Learn Rust Coding en CoddyKit está estructurado para principiantes hasta estudiantes avanzados, así que puedes empezar aquí o desde el inicio y avanzar a tu ritmo. Esta es la lección 3 de 4.
¿Cuánto tiempo toma la lección «Hilos con ámbito»?
La mayoría de las lecciones de CoddyKit toman alrededor de 5–10 minutos. Cada una es compacta e interactiva, así que avanzas constantemente y retomas exactamente por donde dejaste en la web y la app.
¿Puedo escribir y ejecutar código en esta lección de Learn Rust Coding?
Sí. Cada lección de Learn Rust Coding incluye un editor de código integrado, así que escribes y ejecutas código real directamente en tu navegador y obtienes retroalimentación instantánea de IA — sin configuración local necesaria.
Todas las lecciones de este curso
- Canales mpsc
- Compartir estado con Arc/Mutex
- Hilos con ámbito
- Canales Crossbeam