コンテンツにスキップ

Rustのエラー E0594: cannot assign, as it is not declared as mutable(可変でない値への代入)

E0594 は、何か = 値(または +=、-= など)と書いたけれど、その「何か」を読む権限しか持っていない、という意味のエラーです。よくあるのは、mut の付いていない変数の構造体フィールドか、& 参照を通してたどり着いた値です。とてもよく出るエラーで、直し方はほとんどの場合ひとこと、mut です。このページでは、まず直し方を見せてから、その理由を説明します。このシリーズのほかのエラーは、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` は読み取り専用
| ^^^^^^^^ cannot assign
|
help: consider changing this to be mutable
|
7 | let mut p = Player { name: String::from("Ferris"), hp: 10 };
| +++

「p は可変として宣言されていないので、p.hp には代入できません」という意味です。help は「これを可変に変えてはどうか」と提案しています。

このエラーが出るのは、次のプログラムです。

struct Player {
name: String,
hp: u32,
}
fn main() {
let p = Player { name: String::from("Ferris"), hp: 10 };
p.hp = 5; // ❌ `p` は読み取り専用
println!("{} has {} hp", p.name, p.hp);
}

もうひとつの文言、「cannot assign to *x, which is behind a & reference」(& 参照の先にある *x には代入できない)もよく見かけます。エラーコードは同じで、原因が少しだけ違います。こちらは & 参照を持っている場合で、& 参照では見ることしかできません。どちらもこのページで扱います。

8行目の p.hp = 5 の下にある ^^^^^^^^ を見てください。ここでは p のフィールドを変えようとしています。でも p はただの let で作られたので、構造体全体が読み取り専用です。その中のフィールドも、全部読み取り専用になります。

help: の行に直し方が書いてあります。let mut p です。+++ は、足すべき3文字の場所を示しています。

もうひとつの文言のときは、コンパイラは *x += 1 のような行を指して、`x` is a `&` reference, so it cannot be written to(x は & 参照なので、書き込めない)と付け加えます。この場合、あなたは自分が所有する変数を変えようとしているのではありません。借りてきた読み取り専用の参照を通して、ほかの人の値を変えようとしているのです。

図書館の本を思い浮かべてみてください。好きなだけ読めますが、書き込みは禁止です。メモを書きたいなら、自分の本を用意する(let mut)か、持ち主の許可をもらう(&mut)必要があります。

Rust では、特に言わない限り、すべてが読み取り専用です。変数を変えるには let mut が必要で、参照の先を変えるには参照が &mut でなければなりません。構造体を可変にすると可変参照も見てください。

Rust には「可変なフィールド」というものはありません。可変かどうかは、1つ1つのフィールドではなく、変数(または参照)が決めます。だから let mut p なら p のすべてのフィールドが変更でき、ただの let p ならどのフィールドも変更できません。おかげでルールがシンプルです。値がどう宣言されたかを見れば、変更できるかどうかがわかります。

参照のルールは、思わぬ変更からあなたを守ってくれます。&player を関数に渡せば、そのシグネチャが「読むだけです」と約束してくれます。この約束がなかったら、どの関数でもこっそりデータを変えられてしまいますし、読む人と書く人が同時に同じ値に触れるのを、コンパイラが止められなくなります(それが E0502 の話です)。

自分が構造体を所有していて、そのフィールドを変えたいときに使います。たいていはこれが正解です。

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!("{} の HP は {}", p.name, p.hp);
}

実行結果:

Ferris の HP は 8
▶ 実際に試してみよう

タプル(pair.0 = 1;)や配列(grid[2] = 7;)も同じで、それを持っている変数に mut が必要です。Vec の中の構造体なら、team[0].hp = 10; には let mut team が必要です。このケースは E0596 として報告されますが、直し方は同じです。

代入が1回だけなら、mut はそもそも要らないかもしれません。Player { hp: 5, ..other } のように、最初から正しい値で構造体を作ればいいのです。

直し方2:&mut v や iter_mut() でループする

Section titled “直し方2:&mut v や iter_mut() でループする”

コレクションをループして、それぞれの要素を変えたいときに使います。for x in &v や v.iter() が渡すのは & 参照なので、読み取り専用です。

fn main() {
let scores = vec![10, 20, 30];
for x in &scores {
*x += 1; // ❌ `x` は `&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` は `&i32`
| ^^^^^^^ `x` is a `&` reference, so it cannot be written to
|
help: use a mutable iterator instead
|
3 | for x in &mut scores {
| +++

ラベルは「このイテレータは & 参照を返す」「x は & 参照なので書き込めない」と言っていて、help は可変のイテレータを使うよう提案しています。

&mut scores でループし(または .iter_mut() を呼び)、ベクタ自体も 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:?}");
}

実行結果:

[11, 21, 31]
[22, 42, 62]
▶ 実際に試してみよう

&mut scores と scores.iter_mut() は同じことをします。読みやすいほうを選んでください。リストをその場で変える必要がなければ、新しいリストを作る方法もあります。let bigger: Vec<i32> = scores.iter().map(|x| x + 1).collect(); のように書けます。イテレータも見てください。

直し方3:&mut self や &mut の引数を使う

Section titled “直し方3:&mut self や &mut の引数を使う”

メソッドや関数が、受け取ったデータを変えるときに使います。&self のメソッドは構造体を読むことしかできないので、フィールドへの代入は失敗します。

impl Account {
fn deposit(&self, amount: u64) {
self.balance += amount; // ❌ `self` は `&` 参照
}
}
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) {
| +++

「self は & 参照なので書き込めない」という意味で、help は可変参照(&mut self)に変えるよう提案しています。

& の引数を持つふつうの関数でも、同じエラーになります。fn reset(score: &u32) { *score = 0; } は cannot assign to `*score`, which is behind a `&` reference で失敗します。直し方はどちらも同じです。シグネチャに &mut と書き、呼び出し側の変数を let mut にして、呼び出すときに &mut を渡します。

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!("残高: {}", acc.balance);
let mut score = 42;
reset(&mut score);
println!("スコア: {score}");
}

実行結果:

残高: 75
スコア: 0
▶ 実際に試してみよう

メソッドの場合は、呼び出しのときに Rust が &mut を代わりに付けてくれます。acc が let mut でさえあれば、acc.deposit(50) は自動的に acc を可変で借用します。関数の場合は自分で書きます。reset(&mut score) です。&mut u32 がほしい関数に &score を渡すと、E0308(types differ in mutability、型の可変性が違う)になります。メソッドで振る舞いを追加するも見てください。

直し方4(最後の手段):Cell や RefCell

Section titled “直し方4(最後の手段):Cell や RefCell”

&mut をどうしても手に入れられないときだけ使います。たとえば、メソッドが &self と決まっているトレイトを実装するときや、プログラムのあちこちで Rc を通して1つの値を共有しているときです。Cell と RefCell を使うと、& 参照を通して値を変えられます(「内部可変性」、interior mutability)。

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("りんご"), String::from("なし")],
lookups: Cell::new(0),
history: RefCell::new(Vec::new()),
};
println!("なしはある? {}", catalog.contains("なし"));
println!("キウイはある? {}", catalog.contains("キウイ"));
println!("検索回数: {}", catalog.lookups.get());
println!("履歴: {:?}", catalog.history.borrow());
}

実行結果:

なしはある? true
キウイはある? false
検索回数: 2
履歴: ["なし", "キウイ"]
▶ 実際に試してみよう

Cell はカウンターのような小さな Copy の値向け、RefCell はそれ以外向けです。コストについても正直にお伝えしておきます。RefCell は借用チェックをコンパイル時から実行時に移すので、同時に2回可変で借用するとパニックします。ふつうの &mut で済むなら、そちらを選びましょう。スレッドをまたぐときは、代わりに Mutex やアトミック型を使います(共有状態を見てください)。くわしくは RefCell で説明しています。

HashMap の中の値を変えるとき。 *stock.get("apple").unwrap() += 1; は cannot assign to data in a `&` reference(& 参照の中のデータには代入できない)で失敗します。get が返すのは & 参照だからです。直し方:*stock.get_mut("apple").unwrap() += 1; か、*stock.entry("apple").or_insert(0) += 1; と書きます。HashMap も見てください。

Fn として渡したクロージャが変数を変えるとき。 run が F: Fn() を受け取るのに run(|| count += 1) と書くと、cannot assign to `count`, as it is a captured variable in a `Fn` closure(Fn クロージャにキャプチャされた変数なので代入できない)で失敗します。直し方:トレイト境界を F: FnMut() に変えます(run の中では mut f にします)。トレイト境界の選び方を間違えたときも見てください。

static のグローバルなカウンター。 static VISITS: u32 = 0; のあとで VISITS += 1; と書くと、cannot assign to immutable static item `VISITS`(不変の static には代入できない)で失敗します。直し方:static VISITS: AtomicU32 = AtomicU32::new(0); にして VISITS.fetch_add(1, Ordering::Relaxed); と書くか、できればカウンターを &mut で渡して回しましょう。

  • E0596:cannot borrow as mutable(可変として借用できない)。同じ mut の付け忘れですが、代入ではなく list.push(x) のようなメソッド呼び出しのときに出ます。
  • E0384:cannot assign twice to immutable variable(不変の変数に2回代入できない)。フィールドや参照の先ではなく、変数全体に代入した(x = 5;)ときに出ます。
  • E0308:mismatched types(型の不一致)。&mut x が必要なところに &x を渡すと、types differ in mutability と言われます。
  • E0502:&mut で借用できたものの、別の場所がまだその値を読んでいる。
  • E0507:参照の先にある値をムーブできない。これも「読み取り専用」のルールで、今度は書き込みではなくムーブの話です。
  • コンパイルエラーの一覧:エラー解説ページの一覧です。

理解度チェック

1. let user = User { age: 30 }; user.age = 31; で E0594 が出ました。どう直しますか?

2. for p in &prices { *p += 1; } が失敗します。何を変えればいいですか?

3. メソッド fn rename(&self, n: String) { self.name = n; } で E0594 が出ました。ふつうはどう直しますか?

スコア: 0 / 3