O que faz e quando usar
thread::scope (estável desde Rust 1.63) permite criar threads que podem referenciar dados da stack da thread que as criou — sem precisar de Arc, clone ou 'static. Isso é possível porque thread::scope garante que todas as threads criadas dentro do escopo terminam antes que a função retorne, implementando o conceito de concorrência estruturada (structured concurrency).
Sem scope (thread::spawn): Com scope (thread::scope):
┌─────────────────────┐ ┌──────────────────────────┐
│ let dados = vec![..] │ │ let dados = vec![..] │
│ │ │ thread::scope(|s| { │
│ // Precisa de Arc │ │ s.spawn(|| &dados); │◄─ borrow OK!
│ // ou move │ │ s.spawn(|| &dados); │◄─ borrow OK!
│ thread::spawn(move || │ │ }); // espera todas │
│ // owns dados │ │ // dados ainda vivo aqui│
└────────────────────────┘ └──────────────────────────┘
Use thread::scope quando:
- Você quer que threads emprestem dados locais sem transferir ownership.
- Não quer o overhead cognitivo e de performance de
Arc<T>. - Quer garantir que todas as threads terminam antes de continuar (structured concurrency).
- Está fazendo processamento paralelo de dados em uma fatia (slice) ou vetor.
Tipos e Funções Principais
| Item | Descrição |
|---|---|
thread::scope(f) | Cria um escopo; f recebe um &Scope |
scope.spawn(f) | Cria uma thread dentro do escopo |
ScopedJoinHandle<T> | Handle para o resultado da thread escopo |
handle.join() | Aguarda a thread terminar (chamado automaticamente no fim do escopo) |
Importante: Ao final de thread::scope, todas as threads são automaticamente joined. Se qualquer thread entrar em panic, o panic é propagado para a thread chamadora.
Exemplos de Código
Empréstimo básico de dados da stack
use std::thread;
fn main() {
let numeros = vec![1, 2, 3, 4, 5];
let mensagem = String::from("processando");
// thread::scope garante que as threads terminam antes de retornar
thread::scope(|s| {
// Esta thread pode emprestar &numeros — sem Arc, sem move!
s.spawn(|| {
let soma: i32 = numeros.iter().sum();
println!("{}: soma = {}", mensagem, soma);
});
// Outra thread também pode emprestar os mesmos dados
s.spawn(|| {
let max = numeros.iter().max().unwrap();
println!("{}: máximo = {}", mensagem, max);
});
}); // Todas as threads são joined aqui automaticamente
// numeros e mensagem ainda estão disponíveis!
println!("Dados originais: {:?}", numeros);
println!("Mensagem: {}", mensagem);
}
Comparação: spawn vs scope
use std::sync::Arc;
use std::thread;
fn main() {
let dados = vec![10, 20, 30, 40, 50];
// COM thread::spawn — precisa de Arc + clone
{
let dados = Arc::new(dados.clone());
let d1 = Arc::clone(&dados);
let h1 = thread::spawn(move || {
println!("spawn: soma = {}", d1.iter().sum::<i32>());
});
let d2 = Arc::clone(&dados);
let h2 = thread::spawn(move || {
println!("spawn: len = {}", d2.len());
});
h1.join().unwrap();
h2.join().unwrap();
}
// COM thread::scope — borrow direto, sem Arc
{
thread::scope(|s| {
s.spawn(|| {
println!("scope: soma = {}", dados.iter().sum::<i32>());
});
s.spawn(|| {
println!("scope: len = {}", dados.len());
});
});
}
// Muito mais simples e eficiente!
}
Escrita concorrente com fatias mutáveis
Uma das maiores vantagens de scoped threads: dividir um slice mutável entre threads sem locks:
use std::thread;
fn main() {
let mut dados = vec![0u64; 12];
// Dividir o vetor em fatias mutáveis — cada thread escreve na sua fatia
thread::scope(|s| {
for (i, chunk) in dados.chunks_mut(3).enumerate() {
s.spawn(move || {
for (j, valor) in chunk.iter_mut().enumerate() {
*valor = (i * 3 + j) as u64 * 10;
}
println!("Thread {} preencheu {:?}", i, chunk);
});
}
});
println!("Resultado: {:?}", dados);
// [0, 10, 20, 30, 40, 50, 60, 70, 80, 90, 100, 110]
}
Processamento paralelo de dados
use std::thread;
fn processar_item(item: &str) -> usize {
// Simular processamento custoso
std::thread::sleep(std::time::Duration::from_millis(50));
item.len()
}
fn main() {
let itens = vec![
"Rust", "Brasil", "Concorrência", "Segurança",
"Performance", "Threads", "Escopo", "Paralelo",
];
let resultados: Vec<usize> = thread::scope(|s| {
let handles: Vec<_> = itens
.iter()
.map(|item| {
s.spawn(move || processar_item(item))
})
.collect();
handles
.into_iter()
.map(|h| h.join().unwrap())
.collect()
});
for (item, resultado) in itens.iter().zip(resultados.iter()) {
println!("{}: {} chars", item, resultado);
}
}
Acessando múltiplas referências
use std::thread;
struct DadosApp {
usuarios: Vec<String>,
config: Config,
}
struct Config {
max_threads: usize,
timeout_ms: u64,
}
fn main() {
let app = DadosApp {
usuarios: vec![
"Alice".into(), "Bruno".into(), "Carla".into(),
],
config: Config {
max_threads: 4,
timeout_ms: 5000,
},
};
thread::scope(|s| {
// Thread 1: lê usuários
s.spawn(|| {
for user in &app.usuarios {
println!("Usuário: {}", user);
}
});
// Thread 2: lê config (pode ler ao mesmo tempo — é referência imutável)
s.spawn(|| {
println!(
"Config: max_threads={}, timeout={}ms",
app.config.max_threads, app.config.timeout_ms
);
});
});
// app ainda disponível
println!("Total de usuários: {}", app.usuarios.len());
}
Retornando valores de scoped threads
use std::thread;
fn main() {
let dados = vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
// scope pode retornar um valor
let (soma, produto) = thread::scope(|s| {
let h_soma = s.spawn(|| -> i64 {
dados.iter().map(|x| *x as i64).sum()
});
let h_produto = s.spawn(|| -> i64 {
dados.iter().map(|x| *x as i64).product()
});
let soma = h_soma.join().unwrap();
let produto = h_produto.join().unwrap();
(soma, produto)
});
println!("Soma: {}, Produto: {}", soma, produto);
// Soma: 55, Produto: 3628800
}
Tratando panics em scoped threads
use std::thread;
fn main() {
let resultado = std::panic::catch_unwind(|| {
thread::scope(|s| {
s.spawn(|| {
println!("Thread normal executando");
});
s.spawn(|| {
panic!("Ops! Thread em pânico!");
});
s.spawn(|| {
println!("Outra thread executando");
});
});
// Se qualquer thread entrou em panic, scope propaga o panic aqui
});
match resultado {
Ok(()) => println!("Todas as threads completaram com sucesso"),
Err(_) => println!("Pelo menos uma thread entrou em pânico!"),
}
}
Padrões Comuns e Anti-padrões
Padrão: map-reduce paralelo com scope
use std::thread;
fn main() {
let dados: Vec<u64> = (1..=1_000_000).collect();
let num_threads = 8;
let chunk_size = dados.len() / num_threads;
let soma_total: u64 = thread::scope(|s| {
let handles: Vec<_> = dados
.chunks(chunk_size)
.map(|chunk| {
s.spawn(|| -> u64 {
chunk.iter().sum() // borrow direto do chunk!
})
})
.collect();
handles
.into_iter()
.map(|h| h.join().unwrap())
.sum()
});
println!("Soma: {}", soma_total);
// Soma: 500000500000
}
Anti-padrão: usar scope onde spawn seria melhor
use std::thread;
use std::time::Duration;
fn main() {
// ERRADO: usar scope para threads de longa duração que não precisam
// acessar dados locais — scope bloqueia até todas terminarem!
//
// thread::scope(|s| {
// s.spawn(|| {
// loop { /* servidor rodando para sempre */ }
// });
// }); // Nunca retorna!
// CORRETO: usar spawn para threads independentes de longa duração
let _handle = thread::spawn(|| {
// Background worker
thread::sleep(Duration::from_secs(1));
});
// CORRETO: usar scope para trabalho paralelo de curta duração
let dados = vec![1, 2, 3, 4, 5];
thread::scope(|s| {
for chunk in dados.chunks(2) {
s.spawn(|| {
println!("Processando: {:?}", chunk);
});
}
});
}
Anti-padrão: tentar mover dados para fora do scope
use std::thread;
fn main() {
let dados = vec![1, 2, 3];
thread::scope(|s| {
// ERRO: não pode mover dados para dentro de uma scoped thread
// se o dado é emprestado por outra thread no mesmo escopo
// Isso funciona (apenas uma thread usa o dado com move):
// s.spawn(move || {
// println!("{:?}", dados);
// });
// Isso funciona (múltiplas threads com referência):
s.spawn(|| {
println!("Thread 1: {:?}", &dados);
});
s.spawn(|| {
println!("Thread 2: {:?}", &dados);
});
});
println!("Ainda disponível: {:?}", dados);
}
Garantias de Thread Safety
thread::scopegarante que todas as threads criadas terminam antes de retornar (structured concurrency).- Threads dentro do scope podem emprestar dados com lifetimes menores que
'static— diferente dethread::spawn. - Se qualquer thread entrar em panic,
scopeaguarda as demais e então propaga o panic. chunks_mut()com scoped threads é a forma segura de escrever em fatias diferentes de um vetor em paralelo, sem locks.- A closure passada para
scope.spawn()precisa serSend(os dados capturados devem ser transferíveis entre threads).
Veja Também
- std::thread em Rust —
thread::spawnpara threads'static - Mutex e RwLock em Rust — proteger dados quando scope não basta
- Send e Sync — traits necessárias para dados em threads
- Padrões de Thread Safety — quando usar scope vs Arc
- Concorrência em Rust — tutorial completo de concorrência
- Documentação oficial:
std::thread::scope