Rustのエラー E0384: cannot assign twice to immutable variable(不変変数への再代入)
E0384 は、変数に一度値を入れたあとで別の値を入れ直そうとしたけれど、その変数が mut 付きで宣言されていなかった、という意味のエラーです。Rust では、自分から「変えます」と言わない限り、変数は変更できません。Rust を触り始めて最初の1時間で、ほとんどの人がこのエラーに出会います。直し方はたいてい3文字、mut を足すだけです。このページでは、まず直し方を見せてから、なぜ Rust がこういうしくみになっているのかを説明します。このシリーズのほかのエラーは、Rustのコンパイルエラー解説を見てください。
エラーメッセージ
Section titled “エラーメッセージ”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」(不変の引数には代入できない)になります。エラーコードも、考え方も同じです。
エラーの意味
Section titled “エラーの意味”コンパイラは2か所を指しています。
- 2行目(
first assignment to scoreのラベル):ここでscoreに値が入りました。let mutではなくletと書いてあるので、この値はもう変えられません。 - 4行目(
^^^^の印):ここで新しい値を入れようとしたので、Rust が断っています。
help: の部分に、もう直し方が書いてあります。let mut score = 10; です。+++ は、足すべき3文字の場所を示しています。
let はボールペンで書くこと、let mut は鉛筆で書くことだと考えてみてください。どちらも間違いではありません。でも、ボールペンで書いたものは、あとから消せませんよね。
+=、-=、*= なども同じルールです。count += 1 も代入なので、やはり mut が必要です。
Rust がこうする理由
Section titled “Rust がこうする理由”Rust では、どの変数もデフォルトで不変(immutable)です。変更したいときは、mut で自分から選ぶ必要があります。くわしくは変数のページで説明しています。
厳しく聞こえるかもしれませんが、これがとても助けになります。let total = ... と書いてあれば、total はスコープの終わりまでその値のままだとわかります。関数の残りを全部読んで、どこかで変わっていないか確かめる必要はありません。逆に let mut があれば、「これは変わるから注意してね」というはっきりした合図になります。
本物のバグも防いでくれます。コピーを変えるつもりで元の値を書き換えてしまったときや、うっかり同じ名前を使い回したときに、コンパイラが止めてくれるのです。さらに、一度も変更されない mut 変数には警告(variable does not need to be mutable、「この変数は可変である必要がない」)が出ます。だから、どちらの方向でも合図が正直に保たれます。
直し方1:mut を付ける
Section titled “直し方1:mut を付ける”変数が本当に時間とともに変わるべきとき、つまりカウンター、合計、スコア、状態などに使います。たいていはこれが正解で、コンパイラが提案しているのもこの方法です。
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 を渡します。関数と借用も見てみてください。
よく出会う場面
Section titled “よく出会う場面”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(); と書きます。イテレータも見てください。
関連するエラー
Section titled “関連するエラー”- E0596:cannot borrow as mutable, as it is not declared as mutable(可変として宣言されていないので、可変で借用できない)。同じ「
mutの付け忘れ」ですが、代入ではなく.push()のような値を変えるメソッドを呼んだときに出ます。 - E0594:cannot assign to
p.hp, aspis not declared as mutable(pが可変でないのでp.hpに代入できない)。構造体のフィールドに代入したとき(p.hp = 5;)や、&参照を通して書き込んだときに出ます。直し方:let mut pにするか、&mut参照を使います。構造体を可変にするも見てください。 - E0502:読み取り用に借用されている最中に、値を変えようとした。
- E0382:ムーブしたあとの値を使った。
- コンパイルエラーの一覧:エラー解説ページの一覧です。
理解度チェック
Section titled “理解度チェック”理解度チェック
1. let lives = 3; と書いて、あとで lives -= 1; としました。なぜ E0384 になるのでしょうか?
2. name を一度だけ整えて(前後の空白を取って大文字にして)、そのあとはもう変えません。いちばん読みやすい書き方はどれですか?
3. fn halve(n: u32) の中で n /= 2; として E0384 が出ました。呼び出し側を変えずに済む直し方はどれですか?
スコア: 0 / 3
さらに詳しく
Section titled “さらに詳しく”- E0384 — Rust error codes index(英語):このエラーの公式の説明です。例も載っています。
- 変数と可変性 — The Rust Programming Language 日本語版:
let、mut、定数、シャドーイングを説明している章です。 - ミュータビリティ — Rust By Example 日本語版:
letとlet mutの違いがわかる、短い実行できる例です。 - 値の凍結 — Rust By Example 日本語版:可変な変数を不変な変数でシャドーイングすると、値が「凍結」されるしくみです。