Erro E0594 do Rust: cannot assign, as it is not declared as mutable (sem mut)
E0594 significa que você escreveu alguma_coisa = valor (ou +=, -=, …), mas só tem permissão para ler essa coisa. Normalmente é um campo de struct em uma variável sem mut, ou um valor que você acessa através de uma referência &. É um erro muito comum, e a correção quase sempre é uma palavra só: mut. Esta página mostra a correção primeiro e depois explica. Para ver os outros erros desta série, acesse Erros do compilador do Rust.
A mensagem de erro
Seção intitulada “A mensagem de erro”error[E0594]: cannot assign to `p.hp`, as `p` is not declared as mutable --> src/main.rs:8:5 |8 | p.hp = 5; // ❌ `p` é só leitura | ^^^^^^^^ cannot assign |help: consider changing this to be mutable |7 | let mut p = Player { name: String::from("Ferris"), hp: 10 }; | +++O compilador diz que não pode atribuir a p.hp porque p não foi declarada como mutável, e sugere adicionar mut. Este é o programa que causa o erro:
struct Player { name: String, hp: u32,}
fn main() { let p = Player { name: String::from("Ferris"), hp: 10 }; p.hp = 5; // ❌ `p` é só leitura println!("{} has {} hp", p.name, p.hp);}Você também vai ver bastante uma segunda versão da mensagem: “cannot assign to *x, which is behind a & reference” (não é possível atribuir a *x, que está atrás de uma referência &). É o mesmo código de erro, com uma causa um pouco diferente: ali você tem uma referência &, e uma referência & só deixa olhar. As duas situações estão explicadas abaixo.
O que significa
Seção intitulada “O que significa”Olhe o ^^^^^^^^ embaixo de p.hp = 5 na linha 8. Você está mudando um campo de p. Mas p foi criada com um let simples, então a struct inteira é só leitura, incluindo todos os campos dentro dela.
A linha help: mostra a correção: let mut p. Os +++ marcam os três caracteres que você precisa adicionar.
Na segunda versão, o compilador aponta para uma linha como *x += 1 e acrescenta `x` is a `&` reference, so it cannot be written to (x é uma referência &, então não dá para escrever nela). Você não está mudando uma variável que é sua. Está tentando mudar o valor de outra pessoa através de uma referência emprestada, que é só leitura.
Pense em um livro da biblioteca. Você pode ler à vontade, mas não pode escrever nele. Para fazer anotações, você precisa da sua própria cópia (let mut) ou da permissão do dono (&mut).
Por que o Rust faz isso
Seção intitulada “Por que o Rust faz isso”No Rust, tudo é só leitura, a menos que você diga o contrário. Uma variável precisa de let mut para mudar, e uma referência precisa ser &mut para mudar aquilo para o qual aponta. Veja Tornando uma struct mutável e Referências mutáveis.
Não existe “campo mutável” no Rust. A mutabilidade pertence à variável (ou à referência), não a campos isolados. Então let mut p torna todos os campos de p alteráveis, e um let p simples não deixa mudar nenhum. Isso mantém a regra simples: olhe como o valor foi declarado e você já sabe se ele pode mudar.
A regra das referências protege você de surpresas. Se você passa &player para uma função, a assinatura dela promete que ela só vai ler. Sem essa promessa, qualquer função poderia mudar seus dados sem avisar, e o compilador não conseguiria impedir que alguém lesse e alguém escrevesse o mesmo valor ao mesmo tempo (é disso que trata o E0502).
Como corrigir
Seção intitulada “Como corrigir”Correção 1: Adicione mut à variável
Seção intitulada “Correção 1: Adicione mut à variável”Escolha esta quando a struct é sua e você quer mudar os campos dela. É a correção certa na maioria das vezes.
struct Player { name: String, hp: u32,}
fn main() { let mut p = Player { name: String::from("Ferris"), hp: 10 }; p.hp = 5; p.hp += 3; println!("{} tem {} de hp", p.name, p.hp);}Saída:
Ferris tem 8 de hpO mesmo vale para tuplas (pair.0 = 1;) e arrays (grid[2] = 7;): a variável que guarda o valor precisa de mut. Para uma struct dentro de um Vec, team[0].hp = 10; precisa de let mut team; esse caso aparece como E0596, mas a correção é a mesma.
Se você só atribui uma vez, talvez nem precise de mut: crie a struct já com o valor certo, como Player { hp: 5, ..other }.
Correção 2: Percorra com &mut v ou iter_mut()
Seção intitulada “Correção 2: Percorra com &mut v ou iter_mut()”Escolha esta quando você percorre uma coleção e quer mudar cada item. for x in &v e v.iter() entregam referências &, que são só leitura:
fn main() { let scores = vec![10, 20, 30]; for x in &scores { *x += 1; // ❌ `x` é um `&i32` } println!("{scores:?}");}error[E0594]: cannot assign to `*x`, which is behind a `&` reference --> src/main.rs:4:9 |3 | for x in &scores { | ------- this iterator yields `&` references4 | *x += 1; // ❌ `x` é um `&i32` | ^^^^^^^ `x` is a `&` reference, so it cannot be written to |help: use a mutable iterator instead |3 | for x in &mut scores { | +++O compilador mostra que esse iterador entrega referências & e sugere usar um iterador mutável. Percorra &mut scores (ou chame .iter_mut()) e torne o próprio vetor mut:
fn main() { let mut scores = vec![10, 20, 30]; for x in &mut scores { *x += 1; } println!("{scores:?}");
for x in scores.iter_mut() { *x *= 2; } println!("{scores:?}");}Saída:
[11, 21, 31][22, 42, 62]&mut scores e scores.iter_mut() fazem a mesma coisa; escolha o que ficar mais legível. Se você não precisa mudar a lista no lugar, criar uma lista nova também funciona: let bigger: Vec<i32> = scores.iter().map(|x| x + 1).collect();. Veja Iteradores.
Correção 3: Use &mut self ou um parâmetro &mut
Seção intitulada “Correção 3: Use &mut self ou um parâmetro &mut”Escolha esta quando um método ou função muda dados que recebeu. Um método com &self só pode ler a struct, então atribuir a um campo falha:
impl Account { fn deposit(&self, amount: u64) { self.balance += amount; // ❌ `self` é uma referência `&` }}error[E0594]: cannot assign to `self.balance`, which is behind a `&` reference --> src/main.rs:7:9 |7 | self.balance += amount; | ^^^^^^^^^^^^^^^^^^^^^^ `self` is a `&` reference, so it cannot be written to |help: consider changing this to be a mutable reference |6 | fn deposit(&mut self, amount: u64) { | +++Uma função comum com um parâmetro & dá o mesmo erro: fn reset(score: &u32) { *score = 0; } falha com cannot assign to `*score`, which is behind a `&` reference. A correção é igual nos dois casos: escreva &mut na assinatura, torne let mut a variável de quem chama e passe &mut na chamada.
struct Account { balance: u64,}
impl Account { fn deposit(&mut self, amount: u64) { self.balance += amount; }}
fn reset(score: &mut u32) { *score = 0;}
fn main() { let mut acc = Account { balance: 0 }; acc.deposit(50); acc.deposit(25); println!("Saldo: {}", acc.balance);
let mut score = 42; reset(&mut score); println!("Pontuação: {score}");}Saída:
Saldo: 75Pontuação: 0Nos métodos, o Rust escreve o &mut para você na chamada: acc.deposit(50) pega acc emprestado como mutável sozinho, desde que acc seja let mut. Nas funções, você mesmo escreve: reset(&mut score). Se você passar &score para uma função que quer &mut u32, recebe o E0308 (types differ in mutability, os tipos diferem na mutabilidade). Veja Adicionando comportamento com métodos.
Correção 4 (último recurso): Cell ou RefCell
Seção intitulada “Correção 4 (último recurso): Cell ou RefCell”Escolha esta só quando você não consegue ter um &mut: por exemplo, você implementa uma trait cujo método é fixo como &self, ou várias partes do programa compartilham um valor através de Rc. Cell e RefCell permitem mudanças através de uma referência & (“mutabilidade interior”):
use std::cell::{Cell, RefCell};
struct Catalog { items: Vec<String>, lookups: Cell<u32>, history: RefCell<Vec<String>>,}
impl Catalog { fn contains(&self, name: &str) -> bool { self.lookups.set(self.lookups.get() + 1); self.history.borrow_mut().push(name.to_string()); self.items.iter().any(|item| item == name) }}
fn main() { let catalog = Catalog { items: vec![String::from("maçã"), String::from("pera")], lookups: Cell::new(0), history: RefCell::new(Vec::new()), }; println!("tem pera? {}", catalog.contains("pera")); println!("tem kiwi? {}", catalog.contains("kiwi")); println!("Buscas: {}", catalog.lookups.get()); println!("Histórico: {:?}", catalog.history.borrow());}Saída:
tem pera? truetem kiwi? falseBuscas: 2Histórico: ["pera", "kiwi"]Cell serve para valores pequenos que são Copy, como contadores; RefCell serve para todo o resto. Mas é bom ser honesto sobre o custo: RefCell leva a verificação de empréstimos do momento da compilação para o momento da execução, e dá panic se você pegar emprestado como mutável duas vezes ao mesmo tempo. Se um &mut comum resolve, prefira ele. Entre threads, use Mutex ou atômicos (veja Estado compartilhado). Mais detalhes em RefCell.
Onde você costuma ver esse erro
Seção intitulada “Onde você costuma ver esse erro”Mudando um valor dentro de um HashMap. *stock.get("apple").unwrap() += 1; falha com cannot assign to data in a `&` reference, porque get devolve uma referência &. Correção: *stock.get_mut("apple").unwrap() += 1; ou *stock.entry("apple").or_insert(0) += 1;. Veja HashMaps.
Uma closure passada como Fn que muda uma variável. run(|| count += 1), em que run recebe F: Fn(), falha com cannot assign to `count`, as it is a captured variable in a `Fn` closure (count é uma variável capturada em uma closure Fn). Correção: troque o bound para F: FnMut() (e use mut f dentro de run). Veja Quando você escolhe o bound errado.
Um contador global em um static. static VISITS: u32 = 0; e depois VISITS += 1; falha com cannot assign to immutable static item `VISITS`. Correção: use static VISITS: AtomicU32 = AtomicU32::new(0); e VISITS.fetch_add(1, Ordering::Relaxed); ou, melhor ainda, passe um contador como &mut pelo programa.
Erros relacionados
Seção intitulada “Erros relacionados”- E0596: cannot borrow as mutable (não é possível pegar emprestado como mutável). O mesmo
mutesquecido, mas para uma chamada de método comolist.push(x)em vez de uma atribuição. - E0384: cannot assign twice to immutable variable. Você atribui a uma variável inteira (
x = 5;) em vez de a um campo ou através de uma referência. - E0308: mismatched types (tipos incompatíveis),
types differ in mutability, quando você passa&xonde se espera&mut x. - E0502: você tem acesso
&mut, mas outra parte do código ainda está lendo o valor. - E0507: cannot move out of a value behind a reference (não é possível mover para fora de uma referência). A regra do “só leitura” de novo, desta vez para moves em vez de escritas.
- 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. let user = User { age: 30 }; user.age = 31; dá E0594. Qual é a correção?
2. for p in &prices { *p += 1; } falha. O que você muda?
3. Um método fn rename(&self, n: String) { self.name = n; } dá E0594. Qual é a correção mais comum?
Pontuação: 0 / 3
Para saber mais
Seção intitulada “Para saber mais”- E0594 — Rust error codes index (em inglês): a explicação oficial deste erro.
- Defining and Instantiating Structs — The Rust Book (em inglês): por que a instância inteira precisa ser mutável, e não campos isolados.
- References and Borrowing — The Rust Book (em inglês): a parte “Mutable References” explica
&versus&mut. - Method Syntax — The Rust Book (em inglês): quando um método precisa de
&self,&mut selfouself. - RefCell and Interior Mutability — The Rust Book (em inglês): como o
RefCellpermite mudanças através de&, e o custo disso em tempo de execução.