コンテンツにスキップ

Rustのエラー E0384: cannot assign twice to immutable variable(不変変数への再代入)

E0384 は、変数に一度値を入れたあとで別の値を入れ直そうとしたけれど、その変数が mut 付きで宣言されていなかった、という意味のエラーです。Rust では、自分から「変えます」と言わない限り、変数は変更できません。Rust を触り始めて最初の1時間で、ほとんどの人がこのエラーに出会います。直し方はたいてい3文字、mut を足すだけです。このページでは、まず直し方を見せてから、なぜ Rust がこういうしくみになっているのかを説明します。このシリーズのほかのエラーは、Rustのコンパイルエラー解説を見てください。

error[E0384]: cannot assign twice to immutable variable `score`
--> src/main.rs:4:5
|
2 | let score = 10;
| ----- first assignment to `score`
3 | println!("Score: {score}");
4 | score = 15; // ❌ score は mut ではない
| ^^^^^^^^^^ cannot assign twice to immutable variable
|
help: consider making this binding mutable
|
2 | let mut score = 10;
| +++

「cannot assign twice to immutable variable」は「不変(immutable)の変数に2回代入することはできません」という意味です。help は「この束縛を可変にしてはどうか」と提案しています。

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

fn main() {
let score = 10;
println!("Score: {score}");
score = 15; // ❌ score は mut ではない
println!("New score: {score}");
}

変数が関数の引数の場合は、文言が少し変わって 「cannot assign to immutable argument」(不変の引数には代入できない)になります。エラーコードも、考え方も同じです。

コンパイラは2か所を指しています。

  1. 2行目(first assignment to score のラベル):ここで score に値が入りました。let mut ではなく let と書いてあるので、この値はもう変えられません。
  2. 4行目(^^^^ の印):ここで新しい値を入れようとしたので、Rust が断っています。

help: の部分に、もう直し方が書いてあります。let mut score = 10; です。+++ は、足すべき3文字の場所を示しています。

let はボールペンで書くこと、let mut は鉛筆で書くことだと考えてみてください。どちらも間違いではありません。でも、ボールペンで書いたものは、あとから消せませんよね。

+=、-=、*= なども同じルールです。count += 1 も代入なので、やはり mut が必要です。

Rust では、どの変数もデフォルトで不変(immutable)です。変更したいときは、mut で自分から選ぶ必要があります。くわしくは変数のページで説明しています。

厳しく聞こえるかもしれませんが、これがとても助けになります。let total = ... と書いてあれば、total はスコープの終わりまでその値のままだとわかります。関数の残りを全部読んで、どこかで変わっていないか確かめる必要はありません。逆に let mut があれば、「これは変わるから注意してね」というはっきりした合図になります。

本物のバグも防いでくれます。コピーを変えるつもりで元の値を書き換えてしまったときや、うっかり同じ名前を使い回したときに、コンパイラが止めてくれるのです。さらに、一度も変更されない mut 変数には警告(variable does not need to be mutable、「この変数は可変である必要がない」)が出ます。だから、どちらの方向でも合図が正直に保たれます。

変数が本当に時間とともに変わるべきとき、つまりカウンター、合計、スコア、状態などに使います。たいていはこれが正解で、コンパイラが提案しているのもこの方法です。

fn main() {
let mut score = 10;
println!("スコア: {score}");
score = 15; // ✅ これで OK
println!("新しいスコア: {score}");
let mut count = 0;
for _ in 0..3 {
count += 1;
}
println!("カウント: {count}");
}

実行結果:

スコア: 10
新しいスコア: 15
カウント: 3
▶ 実際に試してみよう

典型的なのがカウンターです。let count = 0; と書いて、ループの中で count += 1; とすると、+= の行で E0384 になります。mut を足せば直ります。mut で変数を変更できるようにするも見てみてください。

直し方2:シャドーイングで新しい値を作る

Section titled “直し方2:シャドーイングで新しい値を作る”

変数を本当に変更したいわけではないときに使います。古い値から計算した新しい値がほしくて(何段階かに分けることも多いです)、そのあとはもう変わらない、という場面です。同じ名前で、もう一度 let を書きます。

fn main() {
let price = 20;
let price = price + 5; // 古い price から作った、新しい price
let price = price * 2;
println!("最終価格: {price}");
let input = " 42 ";
let input = input.trim(); // まだ文字列
let input: u32 = input.parse().unwrap(); // ここで数値になる
println!("変換結果: {}", input + 1);
}

実行結果:

最終価格: 50
変換結果: 43
▶ 実際に試してみよう

これをシャドーイング(shadowing)と呼びます。let を書くたびにまったく新しい変数が作られ、古い変数を覆い隠します。うれしい点が2つあります。最後の price は不変なので、あとでうっかり変えてしまう心配がありません。そして、input が &str から u32 になったように、型も変えられます。これは mut にはできないことです。くわしくはシャドーイング:同じ名前を使い回すを見てください。

かんたんな目安です。値が何度も何度も変わる(ループの中や、時間とともに)なら mut。何段階かで値を準備するなら、シャドーイングが読みやすいです。

直し方3:関数の引数に mut を付ける

Section titled “直し方3:関数の引数に mut を付ける”

関数の中で、受け取った引数の自分用のコピーを変えたいときに使います。引数も不変なので、次のコードは失敗します。

fn countdown(n: u32) {
while n > 0 {
println!("{n}...");
n -= 1; // ❌ cannot assign to immutable argument `n`
}
println!("Liftoff!");
}
error[E0384]: cannot assign to immutable argument `n`
--> src/main.rs:4:9
|
4 | n -= 1; // ❌ cannot assign to immutable argument `n`
| ^^^^^^ cannot assign to immutable argument
|
help: consider making this binding mutable
|
1 | fn countdown(mut n: u32) {
| +++

「不変の引数 n には代入できません」という意味で、help は引数を mut n にするよう提案しています。

引数名の前に mut を付けます。

fn countdown(mut n: u32) {
while n > 0 {
println!("{n}...");
n -= 1;
}
println!("発射!");
}
fn main() {
let start = 3;
countdown(start);
println!("start は {start} のまま");
}

実行結果:

3...
2...
1...
発射!
start は 3 のまま
▶ 実際に試してみよう

main の start は変わっていないことに注目してください。mut n で変更できるようになるのは、関数の自分用のコピーだけです。呼び出す側には mut は要りませんし、影響もありません。呼び出し側の変数そのものを関数に変えてほしいときは、&mut u32 を受け取るようにして、&mut start を渡します。関数と借用も見てみてください。

for ループの変数を変えようとしたとき。 ループ変数は、1周ごとに新しく作られる不変の変数です。

for i in 0..5 {
if i == 2 {
i += 1; // ❌ cannot assign twice to immutable variable `i`
}
println!("{i}");
}

直し方:ループのカウンターそのものを変えたいことは、実はあまりありません。continue で飛ばすか、.step_by(2) を使いましょう。細かく制御したいなら、let mut i を使った while ループに切り替えます(for mut i in 0..5 もコンパイルできますが、変わるのはその周のコピーだけで、何も飛ばせません)。break と continue と while ループも見てください。

先に宣言して、あとで代入するとき。 let total; のように値なしで宣言するのは OK で、1回だけ代入できます。2回目の代入で E0384 になります。

let total;
total = 5;
total = 6; // ❌ cannot assign twice to immutable variable `total`

直し方:本当に値が変わるなら let mut total にします。どちらか1つの値を選びたかっただけなら、1つの式で計算しましょう。let total = if big_order { 6 } else { 5 }; のように書けます。if / else も見てください。

合計を足し上げるとき。 let sum = 0; のあとにループで sum += price; と書くのが、いちばんよくある E0384 です。直し方:let mut sum = 0; にするか、変数を使わずに let sum: u32 = prices.iter().sum(); と書きます。イテレータも見てください。

  • E0596:cannot borrow as mutable, as it is not declared as mutable(可変として宣言されていないので、可変で借用できない)。同じ「mut の付け忘れ」ですが、代入ではなく .push() のような値を変えるメソッドを呼んだときに出ます。
  • E0594:cannot assign to p.hp, as p is not declared as mutable(p が可変でないので p.hp に代入できない)。構造体のフィールドに代入したとき(p.hp = 5;)や、& 参照を通して書き込んだときに出ます。直し方:let mut p にするか、&mut 参照を使います。構造体を可変にするも見てください。
  • E0502:読み取り用に借用されている最中に、値を変えようとした。
  • E0382:ムーブしたあとの値を使った。
  • コンパイルエラーの一覧:エラー解説ページの一覧です。

理解度チェック

1. let lives = 3; と書いて、あとで lives -= 1; としました。なぜ E0384 になるのでしょうか?

2. name を一度だけ整えて(前後の空白を取って大文字にして)、そのあとはもう変えません。いちばん読みやすい書き方はどれですか?

3. fn halve(n: u32) の中で n /= 2; として E0384 が出ました。呼び出し側を変えずに済む直し方はどれですか?

スコア: 0 / 3