03. 変数・型・関数・制御フロー

🎯 この章のゴール

  • 不変・可変の変数とシャドーイングを使い分ける
  • 基本的なデータ型を知る
  • 「式」と「文」の違いを理解する

💡 ひとことで言うと

Rust は「書き換えるつもりなら宣言しておいてね」という言語。そして ほとんど全部が式(値を返す) です。

変数: 不変がデフォルト

fn main() {
    let x = 5;
    x = 6; // error[E0384]: cannot assign twice to immutable variable `x`
}
Playground で開く ↗
fn main() {
    let mut x = 5; // mut を付ければ OK
    x = 6;
    println!("{x}");

    const MAX_POINTS: u32 = 100_000; // 定数は型注釈必須、常に不変
    println!("{MAX_POINTS}");
}
Playground で開く ↗

シャドーイング

同じ名前で let し直すと、新しい変数 が前の変数を覆い隠します。型を変えてもOK。

fn main() {
    let spaces = "   ";
    let spaces = spaces.len(); // &str → usize に変身
    println!("{spaces}");
}
Playground で開く ↗

基本のデータ型

分類型例
整数i8〜i128, u8〜u128, isize, usize42, -7, 0xff, 1_000
浮動小数点f32, f64(既定)3.14
真偽値booltrue, false
文字char(Unicode 1 文字, 4 バイト)'🦀', 'あ'
タプル(i32, f64, char)(1, 2.0, 'c')
配列[i32; 3](固定長)[1, 2, 3]
fn main() {
    let tup = (500, 6.4, 'z');
    let (x, y, z) = tup;            // 分解
    let first = tup.0;              // インデックスでアクセス
    let arr = [3; 5];               // [3, 3, 3, 3, 3]
    println!("{x} {y} {z} {first} {}", arr[0]);
}
Playground で開く ↗

配列の範囲外アクセス(arr[10])はコンパイル時に見つからなくても、実行時に panic して安全に停止します。メモリを壊したまま走り続けることはありません。

関数と「式」

fn add(a: i32, b: i32) -> i32 {
    a + b // ; を付けない = この値を返す
}

fn main() {
    let y = {
        let x = 3;
        x + 1 // ブロックも式。y は 4
    };
    println!("{} {y}", add(1, 2));
}
Playground で開く ↗
  • 引数には 型注釈が必須
  • 最後の式の値が戻り値。return は早期リターン用
  • ; を付けると 文 になり、値は ()(ユニット)

制御フロー

fn main() {
    let n = 7;
    let kind = if n % 2 == 0 { "偶数" } else { "奇数" }; // if も式

    let mut count = 0;
    let result = loop {
        count += 1;
        if count == 10 {
            break count * 2; // loop は break で値を返せる
        }
    };

    while count > 0 {
        count -= 3;
    }

    for i in (1..=3).rev() { // 3, 2, 1
        println!("{i}!");
    }
    println!("{kind} {result}");
}
Playground で開く ↗

if の条件は必ず bool。if n { ... } のような書き方(数値を真偽値扱い)はできません。

🧪 やってみよう

下の書きかけコードの TODO を埋めて、▶ 実行 で確かめよう。todo!() のままだと実行時に panic します。

摂氏を華氏に変換する関数を完成させよう(華氏 = 摂氏 × 1.8 + 32)。

fn c_to_f(c: f64) -> f64 {
    todo!("華氏 = 摂氏 × 1.8 + 32")
}

fn main() {
    for c in [0.0, 10.0, 20.0, 30.0] {
        println!("{c}℃ = {}℉", c_to_f(c));
    }
}
Playground で開く ↗
💡 解答例を見る
fn c_to_f(c: f64) -> f64 {
    c * 1.8 + 32.0
}

fn main() {
    for c in [0.0, 10.0, 20.0, 30.0] {
        println!("{c}℃ = {}℉", c_to_f(c));
    }
}
Playground で開く ↗

📝 まとめ

  • 変数は不変がデフォルト。mut で可変、シャドーイングで再定義
  • ; の無い最後の式がブロックや関数の値になる
  • if / loop / match は値を返せる式

✅ 理解度クイズ

Q1
次のコードはコンパイルできる?
fn main() {
    let x = 5;
    x = 6;
    println!("{x}");
}
Q2
次の関数の戻り値は?
fn f() -> i32 {
    let a = 3;
    a * 2
}
Q3
シャドーイング(let で同名変数を再定義)と mut の違いとして正しいものは?
Q4
let n = if cond { 1 } else { "one" }; はコンパイルできる?