Erro E0373 do Rust: closure may outlive the current function (use move)
O E0373 quer dizer que uma closure (função anônima) pega emprestada uma variável local, mas a closure pode continuar rodando (ou continuar existindo) depois que a função dona dessa variável terminou. Você costuma encontrar esse erro na primeira vez que chama thread::spawn ou retorna uma closure de uma função. A boa notícia: o compilador já te diz a correção, e ela quase sempre é uma palavra só, move. Esta página mostra a correção primeiro e depois explica o porquê. Para os outros erros desta série, veja Erros do compilador Rust.
A mensagem de erro
Seção intitulada “A mensagem de erro”error[E0373]: closure may outlive the current function, but it borrows `scores`, which is owned by the current function --> src/main.rs:5:32 |5 | let handle = thread::spawn(|| { | ^^ may outlive borrowed value `scores`6 | println!("Scores: {scores:?}"); // ❌ só pega scores emprestado | ------ `scores` is borrowed here |note: function requires argument type to outlive `'static` --> src/main.rs:5:18 |5 | let handle = thread::spawn(|| { | __________________^6 | | println!("Scores: {scores:?}"); // ❌ só pega scores emprestado7 | | }); | |______^help: to force the closure to take ownership of `scores` (and any other referenced variables), use the `move` keyword |5 | let handle = thread::spawn(move || { | ++++Em português: “a closure pode viver mais que a função atual, mas ela pega emprestado scores, que pertence à função atual”. A ajuda no final sugere a palavra-chave move.
Este é o programa que causa o erro:
use std::thread;
fn main() { let scores = vec![90, 85, 77]; let handle = thread::spawn(|| { println!("Scores: {scores:?}"); // ❌ só pega scores emprestado }); handle.join().unwrap();}Com código async você vai ver o mesmo código com palavras um pouco diferentes: “async block may outlive the current function, but it borrows user_id”. É o mesmo erro, e a correção é a mesma (async move).
O que significa
Seção intitulada “O que significa”Leia os rótulos de cima para baixo:
^^ may outlive borrowed valueaponta para a closure||. O Rust não consegue garantir que a closure termine antes demainseguir em frente.`scores` is borrowed hereaponta para o lugar onde a closure usascores. Como oprintln!só precisa ler o valor, a closure capturou uma referência parascores, não o vetor em si.function requires argument type to outlive 'staticexplica por que isso é um problema:thread::spawnsó aceita closures que podem viver pelo tempo que o programa precisar delas.'staticquer dizer “não pega emprestado nada que possa sumir”.
Imagine emprestar seu caderno para um amigo e dizer “me devolve quando terminar”, bem quando você está prestes a se mudar para outra cidade. Se o seu amigo for devagar, o caderno não tem mais para onde voltar. O Rust recusa esse acordo e pede que você dê o caderno de vez.
Por que o Rust faz isso
Seção intitulada “Por que o Rust faz isso”Uma closure captura as variáveis do jeito mais barato que funciona. Se ela só lê uma variável, ela pega emprestado com &. Isso é ótimo para closures que rodam na hora, como as que você passa para .map() ou .filter(). Você pode ler mais na página de Closures.
Uma thread nova é diferente. Ela pode rodar por qualquer tempo, e main não espera por ela a não ser que você chame join. Se a thread guardasse uma referência para scores e main terminasse primeiro, scores seria liberado e a thread leria memória já liberada. Isso é uma “referência pendurada” (dangling reference), um dos bugs mais comuns em código com threads em C e C++. O Rust impede isso na hora da compilação.
A mesma coisa acontece quando você retorna uma closure. As variáveis locais da função (e os parâmetros dela) são liberadas quando ela retorna. Uma closure que ainda pegasse esses valores emprestados apontaria para o nada.
Como corrigir
Seção intitulada “Como corrigir”Correção 1: Adicione move (a resposta de sempre)
Seção intitulada “Correção 1: Adicione move (a resposta de sempre)”Escolha esta quase sempre. É exatamente o que o rustc sugere. move diz para a closure tomar ownership (posse) das variáveis que ela usa, então elas viajam junto com ela:
use std::thread;
fn main() { let scores = vec![90, 85, 77]; let handle = thread::spawn(move || { let total: i32 = scores.iter().sum(); println!("Notas: {scores:?}, total {total}"); }); handle.join().unwrap();}Saída:
Notas: [90, 85, 77], total 252A mesma correção funciona para uma função que retorna uma closure. Sem move, |x| x + n pega emprestado o parâmetro n, que some assim que make_adder retorna. Com move, cada closure guarda a sua própria cópia de n:
fn make_adder(n: i32) -> impl Fn(i32) -> i32 { move |x| x + n}
fn main() { let add_five = make_adder(5); let add_ten = make_adder(10); println!("{}", add_five(1)); println!("{}", add_ten(1));}Saída:
611move não muda qual trait de closure você recebe. Uma closure move que só lê os seus dados continua sendo Fn e pode ser chamada muitas vezes.
Correção 2: Clone antes, quando você ainda precisa do valor
Seção intitulada “Correção 2: Clone antes, quando você ainda precisa do valor”Depois do move, a variável pertence à closure. Se main usar a variável depois, você recebe o E0382 (“borrow of moved value”, uso de valor movido), e o rustc sugere clonar o valor antes de movê-lo. Faça uma cópia para a closure e fique com o original:
use std::thread;
fn main() { let guests = vec![String::from("Ana"), String::from("Ben")]; let guests_for_thread = guests.clone();
let handle = thread::spawn(move || { println!("A thread vê {} convidados", guests_for_thread.len()); }); handle.join().unwrap();
println!("A main ainda tem: {guests:?}");}Saída:
A thread vê 2 convidadosA main ainda tem: ["Ana", "Ben"]Clonar uma lista pequena é barato e não tem problema nenhum. Se os dados forem grandes, ou se muitas threads precisarem deles, use Arc (Correção 3) para que todas compartilhem uma única cópia.
Correção 3: Compartilhe com Arc entre várias threads
Seção intitulada “Correção 3: Compartilhe com Arc entre várias threads”Escolha esta quando várias threads precisam ler os mesmos dados. Arc (atomically reference counted, “contagem de referências atômica”) deixa muitos donos compartilharem um único valor. Arc::clone só copia um ponteiro e aumenta um contador; os dados em si não são copiados:
use std::sync::Arc;use std::thread;
fn main() { let prices = Arc::new(vec![4.5, 2.0, 3.5]); let mut handles = Vec::new();
for discount in [0.0, 0.1, 0.5] { let prices = Arc::clone(&prices); // um handle por thread handles.push(thread::spawn(move || { let total: f64 = prices.iter().sum(); total * (1.0 - discount) })); }
for handle in handles { println!("Total: {:.2}", handle.join().unwrap()); } println!("Preços: {prices:?}");}Saída:
Total: 10.00Total: 9.00Total: 5.00Preços: [4.5, 2.0, 3.5]Sozinho, o Arc dá um compartilhamento só de leitura. Se as threads também precisarem alterar os dados, use Arc<Mutex<T>>. Veja Concorrência e Estado compartilhado.
Correção 4: Empreste com segurança usando std::thread::scope
Seção intitulada “Correção 4: Empreste com segurança usando std::thread::scope”Escolha esta quando as threads só precisam dos dados para um trabalho curto e você prefere pegar emprestado em vez de mover ou clonar. As threads criadas dentro de thread::scope com certeza terminam antes de scope retornar, então pegar variáveis locais emprestadas é seguro e não precisa de move:
use std::thread;
fn main() { let scores = vec![90, 85, 77, 64]; let (first, second) = scores.split_at(2);
let (a, b) = thread::scope(|s| { let h1 = s.spawn(|| first.iter().sum::<i32>()); let h2 = s.spawn(|| second.iter().sum::<i32>()); (h1.join().unwrap(), h2.join().unwrap()) }); // aqui as duas threads já terminaram
println!("{a} + {b} = {}", a + b); println!("Continua sendo nosso: {scores:?}");}Saída:
175 + 141 = 316Continua sendo nosso: [90, 85, 77, 64]As scoped threads são a resposta mais limpa para “divida o trabalho e depois use os resultados”. A lição de Estado compartilhado começa por elas.
Onde você costuma ver esse erro
Seção intitulada “Onde você costuma ver esse erro”Criando threads em um laço. Até um simples número é pego emprestado se a closure só lê o valor:
for worker in 1..=3 { handles.push(thread::spawn(|| { println!("Worker {worker} started"); // ❌ pega `worker` emprestado }));}Correção: thread::spawn(move || ...). worker é um i32, então o move só copia o valor para dentro de cada thread.
Retornando uma closure em um Box. Box::new(|| greeting.clone()) dentro de uma função que retorna Box<dyn Fn() -> String> pega emprestada a variável local greeting, e o rustc acrescenta note: closure is returned here. Correção: Box::new(move || greeting.clone()). Mais em Fn, FnMut e FnOnce.
Tarefas async (Tokio e companhia). tokio::spawn funciona como thread::spawn: a tarefa pode rodar bem depois que a função atual retornou, então ela precisa de dados 'static. Escrever tokio::spawn(async { ... }) e usar uma variável local lá dentro dá “async block may outlive the current function”. Correção: tokio::spawn(async move { ... }), e clone (ou use Arc::clone) tudo o que você ainda precisar fora da tarefa. Veja Async/await.
Erros relacionados
Seção intitulada “Erros relacionados”- E0382: borrow of moved value: aparece logo depois de você adicionar
movee usar a variável de novo fora da closure. Clone antes (Correção 2) ou useArc(Correção 3). - E0597: borrowed value does not live long enough: um primo próximo. Uma referência ainda está em uso depois que o valor para onde ela aponta foi liberado.
- E0515: cannot return reference to local variable: o mesmo problema de “some quando a função retorna”, com uma referência comum em vez de uma closure.
- E0106: missing lifetime specifier: aparece quando você tenta retornar uma referência (ou um tipo de closure) que pega algo emprestado.
- Todos os erros do compilador: a lista completa de páginas de erros.
Teste o que aprendeu
Seção intitulada “Teste o que aprendeu”Teste o que aprendeu
1. A closure de uma thread imprime name sem usar move. Por que você recebe o E0373?
2. Você adicionou move, mas main ainda precisa imprimir o vetor depois de criar a thread. Qual é a correção mais simples?
3. Qual ferramenta deixa as threads pegarem variáveis locais emprestadas sem move?
Pontuação: 0 / 3
Para saber mais
Seção intitulada “Para saber mais”- E0373 — Rust error codes index (em inglês): a explicação oficial deste erro, com a correção usando
move. - Using Threads to Run Code Simultaneously — The Rust Book (em inglês): a seção “Using move Closures with Threads” mostra exatamente este erro, passo a passo.
- Closures — The Rust Book (em inglês): como as closures capturam variáveis por referência, por referência mutável ou por valor.
movekeyword — standard library docs (em inglês): o quemovefaz com as capturas de uma closure, com exemplos curtos.std::thread::scope— standard library docs (em inglês): scoped threads que podem pegar dados locais emprestados com segurança.