Rust 基礎講座 #2 変数と型 — let と mut、シャドーイング、式で終わる関数

読了 5分

第 1 回で、let で宣言した変数に再代入するとコンパイルエラーになるのを見ました。今回はその地点から始めます。変数、型、関数という基本ですが、Rust はこの 3 つすべてで他の言語と違う選択をしていて、その選択が後の所有権理解の土台になります。

let と mut — 不変が既定値である理由 #

src/main.rs
let count = 1;      // 不変。再代入不可
let mut total = 0;  // 可変。再代入可能
total += count;

多くの言語は「変数は変わるのが基本、定数だけ特別に表示」です。Rust は逆で、変わらないのが基本、変わる変数にだけ mut を付けます。 コードを読むとき、mut のない変数は宣言時の値が最後まで保たれると確信できるので、値が変わりうる箇所だけ追跡すればよく、読むべき範囲が減ります。第 4 回で扱う借用規則もこの不変・可変の区別の上に築かれているので、mut は便宜上の印ではなく、言語全体を貫く軸です。

const は別にあります。コンパイル時に値が確定する本当の定数で、型の表記が必須、慣例として大文字で書きます。

src/main.rs
const MAX_RETRIES: u32 = 3;

シャドーイング — 同じ名前をもう一度宣言する #

mut なしで値を変えているように見える方法が 1 つあります。let で同じ名前を再宣言するシャドーイング(shadowing)です。

src/main.rs
let input = "42";               // &str 型
let input: i32 = input.parse().unwrap(); // 同じ名前、今度は i32 型

再代入と違って新しい変数を作って前の変数を覆い隠す仕組みなので、型まで変えられます。 文字列で受け取った入力をパースして数値として使う上のパターンが代表例です。input_strinput_num と名前を増やす代わりに同じ名前を保つのが Rust のイディオムです。変換前の値を以後使わないことを、名前のレベルで保証する効果もあります。

スカラー型 — 整数はサイズを選ぶ #

分類備考
整数i8i128u8u128isizeusize既定の推論は i32
浮動小数点f32f64既定の推論は f64
真偽値booltruefalse
文字char4 バイトの Unicode スカラー

整数型は多く見えますが、実務感覚は単純です。特別な理由がなければ i32、コレクションのインデックスと長さは usize(ポインタ幅の符号なし整数)、バイトデータは u8 です。char が 1 バイトではなく 4 バイトの Unicode だという点は、文字列を扱う第 7 回で再会します。

オーバーフローの挙動は知っておく価値があります。デバッグビルドでは整数オーバーフローが起きると即座にパニックで止まり、リリースビルドではラップアラウンドして動き続けます。静かにおかしな値になるのを開発段階で捕まえる設計です。意図的にラップや飽和をさせたいときは、wrapping_addsaturating_add のような明示的なメソッドを使います。

型推論が強いので表記は省略することが多いですが、推論が決まらない箇所(上の parse の結果など)では明示が必須です。

タプルと配列 — 固定サイズの束 #

src/main.rs
let point: (f64, f64) = (3.0, 4.0);
let x = point.0;                 // 位置でアクセス
let (px, py) = point;            // 分解束縛

let days: [u32; 3] = [31, 28, 31]; // 長さも型の一部
let jan = days[0];

タプルは異なる型の固定の束、配列は同じ型の固定長の束です。どちらも長さが型の一部なので、コンパイル時にサイズが確定します。実務ではるかによく使う可変長コレクション Vec は、所有権を学んだ後の第 7 回で扱います。配列のインデックスが範囲を外れると、C のように隣のメモリを読むのではなく、パニックで即座に止まります。これも「静かな誤動作より騒がしい失敗」という Rust の一貫した態度です。

関数 — 最後の式が戻り値 #

src/main.rs
fn add(a: i32, b: i32) -> i32 {
    a + b
}

引数の型と戻り値の型(->)は推論に頼らず常に明示します。関数シグネチャがそのままドキュメントという設計です。目を引くのは return がないことですが、誤植ではありません。Rust ではブロックの最後の式にセミコロンを付けないと、その値がブロックの値になります。a + b の後ろにセミコロンを付けると値を捨てる文になり、「i32 が必要なのに () が返った」というコンパイルエラーになります。help 行が「セミコロンを消すように」と教えてくれるので、慌てる必要はありません。

if も式なので値を作ります。三項演算子が別に存在しない理由です。

src/main.rs
let price = if is_member { 800 } else { 1000 };

return は関数の途中で早めに抜けるときだけ使います。この「ほぼすべてが式」という性質は、第 5 回の match で真価を発揮します。

まとめ #

  • 変数は不変が既定で、変わるものにだけ mut を付けます。読む人が追跡する範囲を減らす設計で、後の借用規則の土台になります。
  • シャドーイングは同じ名前を let で再宣言して型まで変えるイディオムです。パース・変換コードでの名前の氾濫を防ぎます。
  • 整数は基本 i32、インデックスは usize、バイトは u8 が実務感覚です。オーバーフローはデバッグでパニック、リリースでラップです。
  • ブロックの最後の式がそのまま値です。セミコロンの有無が文と式を分け、if も値を作ります。
  • 次回はこの講座の山場であり心臓である所有権です。GC なしでメモリが解放される仕組みを正面から扱います。
X