Erro E0502 do Rust: cannot borrow as mutable because it is also borrowed as immutable
O E0502 quer dizer que você tentou alterar um valor enquanto outra parte do código ainda estava lendo esse valor por meio de uma referência. Ele aparece o tempo todo com vetores, e a correção costuma ser mudar uma linha de lugar ou copiar um valor. Esta página mostra a correção primeiro e depois explica por que o Rust faz questão disso. Para os outros erros desta série, veja Erros do compilador Rust.
A mensagem de erro
Seção intitulada “A mensagem de erro”error[E0502]: cannot borrow `v` as mutable because it is also borrowed as immutable --> src/main.rs:4:5 |3 | let first = &v[0]; // uma referência para dentro do Vec | - immutable borrow occurs here4 | v.push(4); // ❌ altera o Vec enquanto `first` está vivo | ^^^^^^^^^ mutable borrow occurs here5 | println!("First: {first}"); // ...porque `first` é usado aqui | ----- immutable borrow later used hereEm português: “não é possível pegar v emprestado como mutável porque ele também está emprestado como imutável”. Os rótulos mostram onde nasce o empréstimo de leitura (linha 3), onde começa o de escrita (linha 4) e onde o de leitura ainda é usado (linha 5).
Este é o programa que gera o erro:
fn main() { let mut v = vec![1, 2, 3]; let first = &v[0]; // uma referência para dentro do Vec v.push(4); // ❌ altera o Vec enquanto `first` está vivo println!("First: {first}"); // ...porque `first` é usado aqui}O que significa
Seção intitulada “O que significa”O compilador aponta três linhas, e você precisa das três para entender o erro:
- Linha 3:
&v[0]cria uma referência só de leitura para dentro dev. Esse é o empréstimo imutável (immutable borrow). - Linha 4:
v.push(4)precisa alterarv, então faz um empréstimo mutável (mutable borrow). - Linha 5:
firsté usado de novo. É esta linha que mantém o primeiro empréstimo vivo durante a linha 4.
Sem a linha 5, o código compilaria. O problema não é os dois empréstimos existirem; é eles se sobreporem.
Por que o Rust faz isso
Seção intitulada “Por que o Rust faz isso”A regra é: vários leitores ou um único escritor, nunca os dois ao mesmo tempo. Você aprendeu isso na página Borrowing (empréstimo).
Com um Vec, o motivo é bem concreto. Um Vec guarda os itens num único bloco de memória. Quando você faz push e esse bloco está cheio, o Vec muda tudo para um bloco novo e maior e libera o antigo. Qualquer referência para o bloco antigo (como first) passa a apontar para memória liberada. Em C++ esse é o famoso bug de “iterator invalidation” (invalidação de iterador), que pode derrubar o programa ou fazer ele ler lixo sem avisar.
É como anotar o endereço de um amigo, e depois ele se mudar para uma casa maior. Sua anotação parece certinha, mas aponta para um prédio vazio. O Rust se recusa a compilar o código em vez de deixar você visitar o prédio vazio.
Como corrigir
Seção intitulada “Como corrigir”Correção 1: Termine de ler antes de escrever
Seção intitulada “Correção 1: Termine de ler antes de escrever”Tente esta primeiro. Desde o Rust 2018, um empréstimo termina no seu último uso, e não na chave de fechamento. Isso se chama non-lexical lifetimes (lifetimes não léxicos). Então é só terminar de usar a referência antes de alterar o vetor:
fn main() { let mut v = vec![1, 2, 3]; let first = &v[0]; println!("Primeiro: {first}"); // último uso de `first`: o empréstimo termina aqui v.push(4); // ✅ ninguém está mais lendo println!("Agora: {:?}", v);}Saída:
Primeiro: 1Agora: [1, 2, 3, 4]Correção 2: Copie o valor para fora
Seção intitulada “Correção 2: Copie o valor para fora”Escolha esta quando você precisa do valor antigo depois da alteração. Pegue uma cópia em vez de uma referência. Para números, basta tirar o &. Para uma String, use .clone():
fn main() { let mut queue = vec![String::from("deploy"), String::from("test")]; let next = queue[0].clone(); // uma cópia nossa, não uma referência queue.push(String::from("release")); println!("Próximo: {next}"); println!("Fila: {:?}", queue);}Saída:
Próximo: deployFila: ["deploy", "test", "release"]Com let first = v[0]; num Vec<i32>, você ganha uma cópia de graça, porque i32 é Copy.
Correção 3: Use iter_mut ou retain para alterar os itens no lugar
Seção intitulada “Correção 3: Use iter_mut ou retain para alterar os itens no lugar”Escolha esta quando você escreveu um loop como for p in prices.iter() e depois tentou prices.remove(i) ou alterar itens dentro dele. Isso também dá E0502. A biblioteca padrão tem métodos feitos exatamente para isso:
fn main() { let mut prices = vec![120, 45, 300, 80];
// altera cada item no lugar for p in prices.iter_mut() { *p = *p * 9 / 10; // 10% de desconto }
// remove os itens que você não quer prices.retain(|&p| p >= 50);
println!("{:?}", prices);}Saída:
[108, 270, 72]iter_mut dá um &mut por item, então só existe um escritor por vez. retain faz a remoção para você, com segurança, numa única passada. Veja Vetores e Iteradores.
Correção 4: Junte as mudanças e aplique depois
Seção intitulada “Correção 4: Junte as mudanças e aplique depois”Escolha esta quando você precisa acrescentar itens com base no que leu. Primeiro leia e junte os resultados, depois escreva numa etapa separada:
fn main() { let mut scores = vec![10, 25, 7, 40]; let bonuses: Vec<i32> = scores.iter().filter(|&&s| s > 20).map(|s| s * 2).collect(); scores.extend(bonuses); // o empréstimo de leitura acabou, então podemos escrever println!("{:?}", scores);}Saída:
[10, 25, 7, 40, 50, 80]O Vec extra gasta um pouco de memória, mas o código fica claro e obviamente correto. Normalmente é uma boa troca.
Correção 5: Use índices
Seção intitulada “Correção 5: Use índices”Deixe esta como último recurso, quando as outras correções não servirem. Um índice é só um número, não um empréstimo, então ele não impede alterações:
fn main() { let mut scores = vec![10, 25, 7]; for i in 0..scores.len() { if scores[i] > 20 { scores.push(scores[i] * 2); } } println!("{:?}", scores);}Saída:
[10, 25, 7, 50]0..scores.len() é calculado uma vez só, antes de o loop começar, então os itens novos não são visitados. Cuidado quando for remover itens por índice: as posições mudam e você pode pular itens ou causar um panic. Para remover, retain (Correção 3) é mais seguro.
Onde você costuma ver esse erro
Seção intitulada “Onde você costuma ver esse erro”Fazer push num Vec enquanto percorre esse mesmo Vec. for s in &scores { scores.push(..) } pega scores emprestado durante o loop inteiro. Correção: junte primeiro e faça extend depois (Correção 4), ou percorra por índice (Correção 5).
Chamar um método &mut self enquanto percorre um campo. Isso é muito comum dentro de blocos impl:
fn audit(&mut self) { for item in &self.items { self.record(item); // ❌ cannot borrow `*self` as mutable because it is also borrowed as immutable }}self.record pega emprestado todo o self como mutável, incluindo self.items. Correção: mexa no outro campo diretamente (self.log.push(item.clone())), porque o Rust deixa você pegar emprestados dois campos diferentes ao mesmo tempo. Ou clone os itens antes. Veja Structs.
Guardar um valor de um HashMap enquanto insere outro. let apples = stock.get("apples").unwrap();, depois stock.insert(..) e depois usar apples dá E0502. Correção: copie o valor para fora com let apples = *stock.get("apples").unwrap(); ou .copied(). Veja HashMaps.
Erros relacionados
Seção intitulada “Erros relacionados”- E0499: dois empréstimos mutáveis ao mesmo tempo. A mesma ideia, com dois escritores em vez de um leitor e um escritor.
- E0382: você moveu um valor e depois o usou. Costuma aparecer quando você corrige o E0502 movendo em vez de emprestar.
- E0106: falta um lifetime numa função que retorna uma referência.
- Todos os erros do compilador: a lista completa de páginas de erro.
Teste o que aprendeu
Seção intitulada “Teste o que aprendeu”Teste o que aprendeu
1. Por que let first = &v[0]; v.push(4); println!("{first}"); não compila?
2. Você quer remover todos os itens menores que 50 de um Vec<i32>. Qual é a correção mais limpa para o E0502?
3. No Rust moderno, quando termina um empréstimo imutável?
Pontuação: 0 / 3
Para saber mais
Seção intitulada “Para saber mais”- E0502 — Rust error codes index (em inglês): a explicação oficial deste erro.
- Storing Lists of Values with Vectors — The Rust Book (em inglês): traz exatamente este exemplo
let first = &v[0]; v.push(..)e explica por que ele falha. - References and Borrowing — The Rust Book (em inglês): a regra “vários leitores ou um escritor” completa.
- Non-lexical lifetimes — The Edition Guide (em inglês): por que um empréstimo agora termina no último uso, e não na chave de fechamento.
Vec— documentação da biblioteca padrão (em inglês):retain,iter_mut,extende os outros métodos que deixam você alterar um vetor com segurança.