Wonderfull Rust

式ベースのプログラミング

「文」と「式」の区別

多くのプログラミング言語では,「文 (Statement)」と「式 (Expression)」を明確に区別する:

  • : 何かのアクションを実行するが,値を返さない(例: if 文,for 文,変数宣言文)
  • : 評価すると値を生成する(例: 1 + 2,関数呼び出し,リテラル)

C, Java, JavaScript, Python などの言語では,iffor は「文」であり,値を返すことはできない.

// JavaScript: if は文なので変数に代入できない
// let x = if (condition) { 1 } else { 2 };  // SyntaxError

// 式として書くには三項演算子を使う必要がある
let x = condition ? 1 : 2;

Rust では,if, match, ブロック {}loop — これらはすべて だ.値を返すことができる.

Rust の式指向設計

if 式

let x = if condition { 1 } else { 2 };

これは Bitterless Rust でも紹介した.ここで重要なのは,これが「三項演算子の代替構文」ではなく,if が本質的に式である という設計に基づいている点だ.

Rust には三項演算子 (? :) が存在しない.それが不要だからだ.if 自体が式なので,三項演算子が果たす役割をそのまま担える.構文を二重に用意する必要がない.

match 式

let label = match status_code {
    200 => "OK",
    404 => "Not Found",
    500 => "Internal Server Error",
    _ => "Unknown",
};

match も式なので,値を直接変数に束縛できる.各アームの評価結果が match 式全体の値になる.

ブロック式

Rust では,波括弧 {} で囲まれたブロックも式であり,ブロック内の最後の式がそのブロックの値になる:

let result = {
    let a = 10;
    let b = 20;
    a + b  // セミコロンなし → この値がブロックの値になる
};
// result == 30

これにより,複雑な計算ロジックを一つのブロック内にまとめつつ,その結果を直接変数に束縛できる.中間変数がスコープの外に漏れることがない.

loop 式

let answer = loop {
    let input = get_user_input();
    if is_valid(&input) {
        break input;  // break に値を渡す → loop 式の値になる
    }
    println!("Invalid input, try again.");
};

break に値を渡すと,それが loop 式の評価結果になる.

関数本体も式

関数の本体はブロック式であり,最後の式(セミコロンなし)がそのまま戻り値になる:

fn double(x: i32) -> i32 {
    x * 2  // return 不要,セミコロンなし
}

return キーワードは途中で関数を抜ける場合にのみ使用する.最後の式で値を返すのが Rust のイディオムだ.

ML 系言語からの影響

Rust の式指向設計は,ML 系言語(OCaml, Haskell, F# など)から強い影響を受けている.ML 系言語では,プログラムの構成要素のほとんどが式であり,値を返す.

この設計がもたらす具体的な利点を見てみよう.

1. 変数の不変束縛と式の組み合わせ

// 条件に応じた値を,不変変数として宣言できる
let message = if user.is_admin() {
    format!("Welcome, admin {}!", user.name)
} else {
    format!("Hello, {}!", user.name)
};
// message は不変.後から変更されない.

命令型スタイルでは,messagemut にして条件分岐内で代入する必要がある:

// 命令型スタイル(Rust でも書けるが,イディオマティックではない)
let mut message = String::new();
if user.is_admin() {
    message = format!("Welcome, admin {}!", user.name);
} else {
    message = format!("Hello, {}!", user.name);
}

式ベースのスタイルでは mut が不要であり,変数の値が初期化後に変更されないことが型レベルで保証される.

2. ネストした式の合成

let category = match score {
    90..=100 => "A",
    80..=89 => "B",
    70..=79 => "C",
    _ => "F",
};

let output = format!(
    "Student: {}, Score: {}, Grade: {}",
    name,
    score,
    category,
);

式は自由に組み合わせて合成できる.match 式の結果を直接 format! マクロに渡すこともできるし,中間変数に束縛してから使うこともできる.

3. 早期 return パターンとの使い分け

Rust の式指向は,早期 return パターンと排他的ではない.両者を適切に使い分けるのが実践的だ:

fn process(input: &str) -> Result<Output, Error> {
    // 早期 return: エラー条件を先に排除
    if input.is_empty() {
        return Err(Error::EmptyInput);
    }

    let parsed = parse(input)?;

    // 式ベース: 正常系のロジック
    let result = match parsed.kind {
        Kind::A => process_a(parsed.data),
        Kind::B => process_b(parsed.data),
        Kind::C => {
            let intermediate = transform(parsed.data);
            finalize(intermediate)
        }
    };

    Ok(result)
}

セミコロンの意味

Rust において,セミコロン ; は式を文に変換する演算子として機能する:

  • x + 1 → 式.i32 型の値を持つ
  • x + 1; → 文.Unit 型 () を返す(値を捨てる)

これが,関数の戻り値を最後の式のセミコロン有無で制御できる理由だ:

fn returns_value() -> i32 {
    42      // 式: i32 を返す
}

fn returns_unit() {
    42;     // 文: 値を捨て,() を返す
}

セミコロンに意味論的な役割を持たせている点は,C 系言語の文法的な区切り記号としてのセミコロンとは本質的に異なる.

Unit 型 ()

戻り値がないことを明示するために,Rust は Unit 型 () を使う.これは ML 系言語から受け継いだ概念で,「値が一つしか存在しない型」だ.値も () と書く.

fn greet(name: &str) -> () {
    println!("Hello, {}!", name);
}
// ↑ -> () は省略可能で,省略した場合と意味は同じ

Unit 型が存在することで,すべての関数が「何らかの型の値を返す」という一貫性が保たれる.void を持つ C 系言語では「値を返さない関数」は型システム上の特殊ケースとして扱わなければならないが,Rust では () という通常の型として統一的に扱える.

この統一性は,ジェネリクスとの組み合わせで効果を発揮する:

// T が () でも問題なく動作する
fn execute<T>(f: impl FnOnce() -> T) -> T {
    f()
}

let value: i32 = execute(|| 42);
let unit: () = execute(|| println!("side effect"));

まとめ

Rust の式指向設計は「構文上の便利さ」ではなく,ML 系言語の理論的基盤に裏打ちされた設計判断だ:

  • if, match, ブロック, loop がすべて式であり値を返す
  • セミコロン が式を文に変換する演算子として機能する
  • Unit 型 () により,すべての関数が値を返すという一貫性が保たれる
  • 不変束縛 と式の組み合わせにより,変数の再代入なしに条件分岐の結果を束縛できる

これらにより,命令型のスタイルと関数型のスタイルを自然に融合させたプログラミングが可能になる.Rust が「マルチパラダイム」と呼ばれる所以の一つがここにある.

本に戻る