カテゴリ: Rust 更新日: 2026/02/08

Rustの戻り値と式ベース設計を徹底解説!セミコロンの有無で変わる関数の挙動

Rustの戻り値と式ベース設計の関係
Rustの戻り値と式ベース設計の関係

先生と生徒の会話形式で理解しよう

生徒

「Rustの関数を見ていると、最後にreturnがなかったり、セミコロンがなかったりするコードをよく見かけます。これって書き忘れじゃないんですか?」

先生

「それはRustが式ベース(expression-based)の言語だからです。多くの処理が値を返す『式』として扱われるため、関数の最後にある式の値がそのまま戻り値になる仕組みなんですよ。」

生徒

「なるほど。でも、セミコロンをうっかり付けてしまったらどうなるんですか?」

先生

「セミコロンを付けるとそれは『文(statement)』になり、値を返さなくなってしまいます。この違いがRustの面白さであり、最初は少し戸惑うポイントですね。詳しく解説していきましょう!」

1. Rustの根幹をなす式ベース設計とは何か

1. Rustの根幹をなす式ベース設計とは何か
1. Rustの根幹をなす式ベース設計とは何か

Rustというプログラミング言語を深く理解するための鍵は、「ほとんどすべてが式である」という設計思想にあります。一般的なプログラミング言語では、変数宣言や代入などの「文(Statement)」が中心となり、プログラムは命令の羅列として進んでいきます。しかし、Rustでは多くの構文が「値を算出する式(Expression)」として機能します。

この式ベース設計の最大の特徴は、コードの断片が評価された結果として、何らかのデータを返すという点です。例えば、他の言語では単なる制御構造であるif文やスコープのブロック{ }でさえも、Rustでは値を返す「式」として扱うことができます。この設計により、一時的な変数への代入を減らし、データの流れを直線的かつ安全に記述することが可能になります。関数における戻り値の扱いも、この大きな設計思想の一部として組み込まれているのです。

2. 文と式の明確な違いとセミコロンの役割

2. 文と式の明確な違いとセミコロンの役割
2. 文と式の明確な違いとセミコロンの役割

Rustにおいて「文」と「式」を区別する境界線は、末尾の「セミコロン(;)」にあります。文(Statement)は、何らかのアクションを実行しますが、値を返しません。例えば、let x = 5;のような変数宣言は文であり、それ自体が値を持つことはありません。一方で、式(Expression)は値を評価し、その結果を次に渡します。数値の5や、計算式のx + 1は式です。

ここで初心者が最も注意すべき点は、式の末尾にセミコロンを付けると、その式が文に変換されてしまうということです。文に変換されると、その式が持っていた評価値は捨てられ、何も返さない状態(ユニット型)になります。Rustの関数では、関数の最後の一行を「セミコロンのない式」にすることで、その値を戻り値として採用します。この独特なルールが、Rustらしい簡潔な記述を支えているのです。

3. 戻り値を決定する暗黙の返却と明示的なリターン

3. 戻り値を決定する暗黙の返却と明示的なリターン
3. 戻り値を決定する暗黙の返却と明示的なリターン

Rustの関数で値を返す方法は二通りあります。一つは、先ほど説明した「関数の最後の式」として記述する方法です。これは「暗黙の戻り値」とも呼ばれ、Rustのコミュニティでは最も推奨される書き方です。returnというキーワードを省略できるため、コードがスッキリとし、関数の目的が「値を算出することである」という意図が明確になります。

もう一つの方法は、returnキーワードを使用する「明示的な戻り値」です。これは主に、関数の途中で処理を打ち切りたい場合(早期リターン)に使用されます。例えば、エラーチェックの結果によって処理を中断してすぐにエラーを返したいときなどに便利です。通常の正常系ルートでは最後の式を使い、例外的なケースではreturnを使うという使い分けが、読みやすいRustコードを書くための秘訣と言えます。


fn main() {
    let score = 85;
    let grade = evaluate_score(score);
    println!("あなたの評価は: {}", grade);
}

fn evaluate_score(n: i32) -> String {
    if n < 0 {
        return String::from("不正な数値"); // 早期リターン
    }

    if n >= 80 {
        String::from("合格") // セミコロンなし:戻り値になる
    } else {
        String::from("不合格") // セミコロンなし:戻り値になる
    }
}

あなたの評価は: 合格

4. if式やブロックを用いた高度な戻り値の制御

4. if式やブロックを用いた高度な戻り値の制御
4. if式やブロックを用いた高度な戻り値の制御

Rustの式ベース設計を活かすと、関数の戻り値を非常に柔軟に制御できます。ifは「式」であるため、条件分岐の結果をそのまま変数に代入したり、関数の戻り値にしたりすることが可能です。これにより、他の言語で見られる「三項演算子」のような記述を、より自然な形で行うことができます。

また、波括弧{ }で囲まれたブロック自体も式として機能します。ブロック内の最後の一行にセミコロンを置かないことで、複雑な計算過程を経て導き出された最終的な値を、そのブロックの評価値として取り出すことができます。この仕組みを理解すると、変数の有効範囲(スコープ)を最小限に抑えつつ、安全に値を生成するコードが書けるようになります。


fn main() {
    let x = 10;
    
    // ブロック全体が式として評価され、結果がyに代入される
    let y = {
        let temp = x * 2;
        temp + 5 // セミコロンがないため、この計算結果がyの値になる
    };

    println!("yの値は: {}", y);
}

yの値は: 25

5. 何も返さない関数とユニット型の真実

5. 何も返さない関数とユニット型の真実
5. 何も返さない関数とユニット型の真実

すべての関数が値を返すわけではありません。画面に文字を表示するだけの場合や、データをファイルに保存するだけの処理を行う関数もあります。このような「戻り値がない」とされる関数も、実は内部的には「ユニット型」と呼ばれる特別な値を返しています。ユニット型は空の括弧()で表記され、「意味のある値を持っていない」という状態を表す型です。

関数の定義で戻り値の型(->の部分)を省略した場合、その関数は暗黙的にユニット型を返すことになります。また、関数の最後の行にセミコロンを付けて文にしてしまった場合も、戻り値はユニット型になります。もし戻り値を期待している関数で、誤って最後にセミコロンを付けてしまうと、型不一致によるコンパイルエラーが発生します。このエラーは初心者が非常によく遭遇するものですが、Rustが「戻り値を正しく扱っているか」を厳密にチェックしてくれている証拠でもあります。


fn main() {
    print_message("こんにちは、Rustの世界!");
}

// 戻り値の型を省略すると、暗黙的に -> () となる
fn print_message(msg: &str) {
    println!("{}", msg);
    // 最後が文(セミコロンあり)なので、何も返さない
}

こんにちは、Rustの世界!

6. 型推論と戻り値の型の厳格な一致

6. 型推論と戻り値の型の厳格な一致
6. 型推論と戻り値の型の厳格な一致

Rustは強力な型推論を持っていますが、関数のシグネチャ(定義部分)における引数と戻り値の型は、必ず明示しなければなりません。これは、関数の入り口と出口を明確にすることで、型推論の曖昧さを排除し、コンパイルの安定性と速度を向上させるためです。一度戻り値の型を定義したら、関数内のすべての実行パスにおいて、その型と一致する値を返す必要があります。

例えば、i32を返すと宣言した関数の中で、ある分岐では数値を返し、別の分岐ではセミコロンを付けて値を返さないようにしてしまうと、コンパイラは即座にエラーを出します。この一貫性の要求が、実行時の予期せぬ動作を防ぐ堅牢なバリアとなります。式ベース設計は自由度を高めますが、型システムというルールの上で成り立つ自由であることを忘れてはいけません。

7. match式を組み合わせた強力な結果の返却

7. match式を組み合わせた強力な結果の返却
7. match式を組み合わせた強力な結果の返却

Rustの真骨頂は、match式と戻り値の組み合わせにあります。matchも式であるため、複数のパターンに基づいて値を分岐させ、その結果をそのまま関数から返すことができます。これは、列挙型(Enum)と組み合わせたときに非常に強力な威力を発揮します。

条件が複雑になればなるほど、if文を重ねるよりもmatch式で網羅的にパターンを記述する方が、コードの安全性が高まります。すべてのケースを網羅しているかどうかをコンパイラがチェックしてくれるため、戻り値の返し忘れや、想定外の状態によるバグを根絶できるのです。以下に、簡単な計算機のような動作をmatch式で実現する例を示します。


enum Operation {
    Add,
    Sub,
    Mul,
}

fn main() {
    let result = calculate(10, 5, Operation::Mul);
    println!("演算結果: {}", result);
}

fn calculate(a: i32, b: i32, op: Operation) -> i32 {
    // match全体が式として評価され、その結果が関数の戻り値になる
    match op {
        Operation::Add => a + b,
        Operation::Sub => a - b,
        Operation::Mul => a * b,
    }
}

演算結果: 50

8. エラー処理とResult型を用いた戻り値の設計

8. エラー処理とResult型を用いた戻り値の設計
8. エラー処理とResult型を用いた戻り値の設計

実用的なRustプログラミングにおいて、最も重要な戻り値の使い方は、成功と失敗の両方を表現できるResult型を返すことです。Rustには他の言語のような例外処理(try-catch)がありません。その代わりに、処理が成功した場合はOk(値)、失敗した場合はErr(原因)を返すという、戻り値によるエラーハンドリングを徹底しています。

この設計は、式ベースの考え方と非常に相性が良いです。関数を呼び出した側は、返ってきた戻り値を必ずチェックしなければならないため、エラーを見逃すことが構造的にあり得ません。戻り値の型を工夫するだけで、メモリの安全性だけでなく、ロジックの安全性まで確保できるのがRustの素晴らしい点です。戻り値と式の関係を正しくマスターすることは、Rustマスターへの最も重要なステップなのです。

カテゴリの一覧へ
新着記事
New1
C++
C++のメンバアクセス演算子を完全解説!初心者でもわかる . → :: の使い方まとめ
New2
Rust
Rustの文字列を極める!&str(文字列スライス)の基本概念とString型との違い
New3
C++
C++のキャスト演算子を完全解説!dynamic_cast・static_cast・const_cast・reinterpret_castを初心者向けに説明
New4
C++
C++開発のIDE選びを完全ガイド!初心者でもわかるCLion・Eclipse CDT・Qt Creator比較
人気記事
No.1
Java&Spring記事人気No1
C++
C++の主要な実装をわかりやすく解説!GCC・Clang・MSVCの違いと特徴
No.2
Java&Spring記事人気No2
C言語
C言語を学ぶ初心者におすすめの環境構築手順【2025年版】
No.3
Java&Spring記事人気No3
C言語
C言語のソースコードとヘッダファイルの役割とは?初心者向けにわかりやすく解説!
No.4
Java&Spring記事人気No4
C言語
C言語をオンラインで実行できる便利なコンパイラサービスまとめ【初心者向け】
No.5
Java&Spring記事人気No5
C言語
C言語開発でよく使われるエディタとIDEランキング【初心者向け完全ガイド】
No.6
Java&Spring記事人気No6
C言語
Visual Studio CodeでC言語を実行する方法【拡張機能の設定と実行手順】
No.7
Java&Spring記事人気No7
C++
C++リンカとコンパイラのオプション設定を完全ガイド!初心者にもわかる開発環境の基礎
No.8
Java&Spring記事人気No8
C言語
C言語の列挙型(enum)の使い方を完全ガイド!初心者でもわかる基本操作