Pular para o conteúdo

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.

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 here
4 | v.push(4); // ❌ altera o Vec enquanto `first` está vivo
| ^^^^^^^^^ mutable borrow occurs here
5 | println!("First: {first}"); // ...porque `first` é usado aqui
| ----- immutable borrow later used here

Em 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 compilador aponta três linhas, e você precisa das três para entender o erro:

  1. Linha 3: &v[0] cria uma referência só de leitura para dentro de v. Esse é o empréstimo imutável (immutable borrow).
  2. Linha 4: v.push(4) precisa alterar v, então faz um empréstimo mutável (mutable borrow).
  3. 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.

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.

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: 1
Agora: [1, 2, 3, 4]
▶ Experimente você mesmo

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: deploy
Fila: ["deploy", "test", "release"]
▶ Experimente você mesmo

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]
▶ Experimente você mesmo

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.

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]
▶ Experimente você mesmo

O Vec extra gasta um pouco de memória, mas o código fica claro e obviamente correto. Normalmente é uma boa troca.

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]
▶ Experimente você mesmo

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.

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.

  • 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

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