Rustのシャドウイング、ただの上書きじゃない!
最近Rust学習にハマっている初学者です。
楽しくてついついzennに投稿しちゃいますがお目汚しをしていたらすみません🙇
本題ですがRustを学び始めると、letを2回使って変数を「上書き」しているように見えるコードに出会います。
fn main() {
let x = 5;
let x = x + 1;
println!("The value of x is: {}", x); // 6が出力される
}
「あれ?変数はデフォルトで不変(immutable)じゃなかったの?」と混乱した方も多いのではないでしょうか。
実はこれ、変数の上書き(再代入)ではなく、シャドウイング(shadowing) というRustの強力な機能なんです。
この記事では、シャドウイングが一体何者で、mutによる可変性とはどう違うのか、そしてどんな時に使うとコードが劇的に改善されるのかを、役立つように解説していきます!
そもそもシャドウイングとは? mutとの決定的な違い
まず、基本をしっかり押さえましょう。
-
mut(再代入): 変数を可変にします。- 同じ型の値を再代入できますが、型は変えられません。ダンボール箱の中身を入れ替えるイメージです。箱自体は同じものです。
-
シャドウイング:
letを使い、同じ名前で全く新しい変数を定義します。- 古い変数は新しい変数に「影で隠され」てアクセスできなくなります。
- 型を変えることができます。古いダンボール箱の横に、同じラベルを貼った新しい箱を置くイメージです。
- 古い変数は新しい変数に「影で隠され」てアクセスできなくなります。
この「型を変えられる」という点が、シャドウイングを理解する上で最も重要なポイントです。
// これはコンパイルエラー!
let mut spaces = " "; // spaces は &str 型
spaces = spaces.len(); // .len() は usize 型を返すため、&str 型の変数には代入できない
mutはあくまで同じ型の中での値の変更しか許可しないという事です。
シャドウイングが輝く!?鉄板ユースケース
シャドウイングを最初に学ぶのに最適な例を2つ紹介します。
1. 文字列から数値へ。型変換の常套手段
ユーザーからの入力は、最初は文字列として受け取ることがほとんどです。
それを数値として扱いたい場合、シャドウイングが解決してくれます。
use std::io;
fn main() {
let mut guess = String::new();
io::stdin()
.read_line(&mut guess)
.expect("Failed to read line");
// ここがポイント!
// guess(String型)を、パースした結果(u32型)でシャドウイング
let guess: u32 = guess.trim().parse().expect("Please type a number!");
println!("You guessed: {}", guess);
}
もしシャドウイングを使わないと、guess_strとguess_numのように、似たような目的の変数が2つできてしまいます。
シャドウイングを使えば、意味的に同じ「ユーザーの推測値」というデータを、処理の段階に応じて最適な型に変化させながら、guessという一貫した名前で扱えるのです。
2. 値を加工して、元の値はもう使わないとき
ある値を少し加工した結果だけが必要で、加工前の値はもう不要、というケースは頻繁にあります。
// 文字列の前後の空白を取り除きたい
let name = " Taro ";
// name(空白あり)を、trimした結果(空白なし)でシャドウイング
let name = name.trim();
println!("Hello, {}!", name); // Hello, Taro!
この処理の後、" Taro "という元の値にアクセスする必要はありません。
シャドウイングすることで、加工後のクリーンなデータだけがnameという名前で残り、コードの見通しが良くなります。
少し踏み込んだシャドウイング活用術
基本的な使い方が分かったところで、もう少し設計的な視点でシャドウイングを見ていきましょう。
1. スコープを利用した一時的なシャドウイング
シャドウイングはスコープを尊重します。
つまり、ブロック内でシャドウイングされた変数は、そのブロックを抜けると元に戻ります。
let x = 10;
println!("ブロックに入る前のx: {}", x); // 10
{
// このブロック内だけでxをシャドウイング
let x = "hello";
println!("ブロック内のx: {}", x); // "hello"
}
println!("ブロックを抜けた後のx: {}", x); // 10
これにより、ある処理ブロック内でのみ一時的に異なる値や型で変数を扱いたい場合に、グローバルな状態を汚さずに済みます。
例えば、関数の引数をブロック内で加工して使うが、関数の最後では元の引数の値を使いたい、といった複雑なケースで役立ちます。
2. mutよりもシャドウイングを好むべき場面
mutとシャドウイング、どちらを使うか迷うことがあります。判断基準は**「状態の変化」なのか「値の変換」なのか**です。
mutが適しているケース
- ループカウンター (
for i in 0..10) - 状態の積み上げ(合計値の計算など)
- 状態を持つオブジェクトのフィールド更新
要するに、**値が何度も変わる可能性がある「状態」**を表現したい場合です。
シャドウイングが適しているケース
- 型変換(この記事で紹介した例)
- 値のサニタイズ(
trim()など) -
Option<T>からTを取り出す
let value = some_option.unwrap(); のように、**値の「性質」や「段階」が変わる「変換」**を行いたい場合です。
シャドウイングを使うと、変換後の値は再び不変(immutable)になります。これにより、意図せず値を変更してしまうバグを防ぎ、Rustの安全性の恩恵を最大限に受けることができます。
まとめ
シャドウイングは、単なる変数の再定義機能ではありません。それは、
- 型変換をスムーズに行い、コードを簡潔にする
- 不必要な可変性(
mut)を減らし、コードの安全性を高める - 変数名の一貫性を保ち、可読性を向上させる
という、Rustの設計思想に深く根差した洗練された機能だと思います。
mutとの違いを正しく理解し、適切な場面でシャドウイングを活用することで、Rustコードはより安全で、より豊かな表現になると思います。
ぜひ、今日から意識して使ってみてください!
Discussion