«Use of moved value» em Rust: por que isso acontece e como lidar com isso
\n\nSe você acabou de começar a estudar Rust, provavelmente já se deparou com a mensagem enigmática: error[E0382]: use of moved value. O compilador reclama, você coça a cabeça, e o código, que é logicamente absolutamente correto, se recusa a compilar. Familiar? Bem-vindo ao mundo da propriedade e do empréstimo — conceitos-chave do Rust que o tornam seguro, mas às vezes deixam até desenvolvedores experientes perplexos.
Este artigo não é uma documentação seca, mas uma análise viva do problema. Vamos examinar por que ocorre o erro use of moved value, com exemplos reais de código e, principalmente, o que fazer com ele. Você aprenderá a pensar como o borrow checker e parará de lutar contra ele. Vamos lá!
\n\nO que é «move» no Rust e por que o compilador é tão rigoroso?
\n\nNo Rust, cada valor tem exatamente um proprietário (owner). Quando você atribui um valor a outra variável ou o passa para uma função, a propriedade é movida (move). Após a movimentação, a variável original se torna inválida — o compilador proíbe o acesso a ela. Essa é a raiz do erro use of moved value.
\n\nVeja o exemplo mais simples:
\n\nlet s1 = String::from("olá");\nlet s2 = s1; // A propriedade é movida de s1 para s2\nprintln!("{}", s1); // ERRO: use of moved value: `s1`\n\nPor que isso? String não é uma simples cópia na pilha. Ela armazena dados no heap. Se o Rust simplesmente copiasse o ponteiro, ao liberar a memória, ambas as variáveis tentariam deletá-la — double free. Em vez disso, o Rust «move» a propriedade: agora s2 é responsável pela memória, e s1 não existe mais do ponto de vista do compilador. Essa é a ideia principal da semântica de movimentação no Rust.
Cenários típicos que levam ao erro
\n\nO erro use of moved value ocorre mais frequentemente em três situações:
- \n
- Passar um valor para uma função \n
- Atribuir um valor a outra variável \n
- Usar um valor em um loop ou iterador \n
Vamos examinar cada caso em detalhes.
\n\n1. Passagem para uma função sem retorno
\n\nfn print_string(s: String) {\n println!("{}", s);\n}\n\nfn main() {\n let my_string = String::from("Rust");\n print_string(my_string); // propriedade movida\n println!("{}", my_string); // erro! use of moved value\n}\n\nApós a chamada de print_string, a propriedade de my_string foi para a função. Quando a função termina, s é deletada, e my_string não é mais válida. O compilador protege você de usar um objeto morto.
2. Atribuição sem cópia
\n\nJá vimos este exemplo com String. Mas é importante: nem todos os tipos se comportam assim. Tipos simples como i32, bool, f64 implementam a trait Copy. Eles não são movidos, mas copiados automaticamente.
let x = 5;\nlet y = x; // cópia, não movimentação\nprintln!("{}", x); // funciona!\n\nTipos que armazenam dados no heap (String, Vec, Box) não implementam Copy — apenas Clone. Portanto, eles são movidos.
3. Iteração sobre uma coleção
\n\nOutra fonte comum de erros são os loops for:
let v = vec![1, 2, 3];\nfor x in v { // v é movido para o iterador\n println!("{}", x);\n}\nprintln!("{:?}", v); // erro! use of moved value\n\nQuando você escreve for x in v, na verdade está chamando into_iter(), que consome o vetor. Após o loop, v não existe mais.
Como corrigir: 4 maneiras comprovadas
\n\nAgora