Pular para o conteúdo

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.

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.

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).

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).

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

O 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 }.

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 `&` references
4 | *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]
▶ Experimente você mesmo

&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.

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: 75
Pontuação: 0
▶ Experimente você mesmo

Nos 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.

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? true
tem kiwi? false
Buscas: 2
Histórico: ["pera", "kiwi"]
▶ Experimente você mesmo

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.

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.

  • E0596: cannot borrow as mutable (não é possível pegar emprestado como mutável). O mesmo mut esquecido, mas para uma chamada de método como list.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 &x onde 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

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