ブロック内でのシャドーイングは外側の変数に反映されない
if文などのブロックの中でletを使って同じ名前の変数を再宣言(シャドーイング)すると、それはブロック内だけで有効な別の変数になります。ブロックを抜けると、外側の変数は変更されないまま残ります。
なぜエラーが出ないのか
出力: inside: 120
(エラーなし)- Rustは何も報告しません。型としても借用としても正しいコードだからです
出力: inside: 120- 実際の出力 — 期待した結果と食い違っている箇所
見つけ方- エラーが出ないので、出力を目で確かめるしかありません。この種の誤りが最も発見が遅れます
このエラーが出る典型パターン
パターン1
1 fn main() { 2 let mut total = 100; 3 let bonus = 20; 4 if bonus > 0 { 5 let total = total + bonus; ^ 6 println!("inside: {}", total); 7 } 8 println!("outside: {}", total); 9 }
letによる再宣言はブロック内だけで有効な新しい変数を作ります。外側のtotalを更新したいなら代入(=)を使います。
直し方: let total = を total = にします。
パターン2
1 fn main() { 2 let mut balance = 500; 3 let deposit = 100; 4 if deposit > 0 { 5 let balance = balance + deposit; ^ 6 println!("inside: {}", balance); 7 } 8 println!("outside: {}", balance); 9 }
letで作った変数はブロックを抜けると消え、外側の変数には一切影響しません。
直し方: let balance = を balance = にします。
パターン3
1 fn main() { 2 let mut level = 1; 3 let gained = 2; 4 if gained > 0 { 5 let level = level + gained; ^ 6 println!("inside: {}", level); 7 } 8 println!("outside: {}", level); 9 }
シャドーイングと再代入は見た目が似ていますが、変数のスコープに与える影響が全く違います。
直し方: let level = を level = にします。
パターン4
1 fn main() { 2 let mut stock = 30; 3 let arrived = 10; 4 if arrived > 0 { 5 let stock = stock + arrived; ^ 6 println!("inside: {}", stock); 7 } 8 println!("outside: {}", stock); 9 }
ブロックの外にも反映させたい更新は、シャドーイングではなくmut変数への代入で行う必要があります。
直し方: let stock = を stock = にします。
パターン5
1 fn main() { 2 let mut hp = 50; 3 let heal = 15; 4 if heal > 0 { 5 let hp = hp + heal; ^ 6 println!("inside: {}", hp); 7 } 8 println!("outside: {}", hp); 9 }
letを付けた瞬間に、それはまったく別の変数として扱われ、外側の変数とは無関係になります。
直し方: let hp = を hp = にします。
よくある誤解
「同じ名前でletし直せば、外側の変数もその値に更新されるはず」という思い込みは誤りです。letによるシャドーイングは常に新しい変数を作る操作であり、既存の変数への代入(mut変数への=での再代入)とは別物です。
実務での勘所
Rustがシャドーイングとmutによる再代入という2つの仕組みを両方持っているのは、それぞれ役割が違うためです。mutによる再代入は同じ型のまま値だけを更新しますが、シャドーイングは型そのものを変えることさえできます。let input = "42"; let input: i32 = input.parse().unwrap();のように、文字列から数値へ変換する一連の処理を、同じ変数名を使い回しながら書けるのがシャドーイングの利点です。ただしこの利便性は同じスコープの中でだけ有効で、ブロックをまたぐと別々の変数として扱われてしまいます。