Rustの関数を徹底解説!引数と戻り値の書き方から型推論の仕組みまで
生徒
「Rustの関数って、他の言語と比べて引数の書き方が厳しい気がするのですが、コツはありますか?」
先生
「そうですね。Rustでは静的型付けを重視しているため、関数の引数には必ず型アノテーションが必要です。でも、これがあるおかげで、コンパイル時に多くのミスを防げるんですよ。」
生徒
「戻り値の書き方も少し特殊ですよね。returnを使わなくても値を返せると聞きました。」
先生
「鋭いですね!Rustには文(Statement)と式(Expression)という明確な違いがあります。これらを理解すると、Rustらしい簡潔で安全なコードが書けるようになります。具体的な書き方を見ていきましょう!」
1. Rustにおける関数の基本構造と定義方法
Rustプログラミングにおいて、処理をひとまとめにする最小単位が関数です。関数を定義するには、アルファベットの小文字で構成されるキーワードfnを使用します。Rustの命名規則では、関数名は「スネークケース」と呼ばれる、単語をアンダースコアで繋ぐ形式(例:calculate_value)が標準とされています。
関数を定義する場所は、呼び出し元よりも前であっても後であっても構いません。Rustのコンパイラはファイル全体をスキャンするため、C言語のようなプロトタイプ宣言を必要とせず、自由な順序でコードを整理できるのが大きな利点です。プログラムの実行は、常に特別な名前を持つmain関数から始まります。まずは、引数を持たない最もシンプルな関数を定義し、それを呼び出す流れを理解しましょう。関数名の後に丸括弧をつけることで、その処理を実行できます。
fn main() {
println!("プログラムを開始します。");
show_message();
}
fn show_message() {
println!("こちらは独自の関数内の処理です。");
}
プログラムを開始します。
こちらは独自の関数内の処理です。
2. 引数の指定と型アノテーションの義務化
Rustの関数において、外部から値を受け取るための窓口が「引数」です。Rustの設計思想において、関数のシグネチャ(名前、引数、戻り値の定義)は非常に重視されています。そのため、関数の定義では、各引数に対して必ず「型」を明示しなければなりません。これを「型アノテーション」と呼びます。変数宣言(let)では型推論が働いて省略できる場合が多いですが、関数定義では省略が許されません。
なぜ引数の型を強制するのでしょうか。それは、関数がどのようなデータを受け取るかをコンパイラと開発者の両方が明確に認識できるようにするためです。これにより、関数内部での処理の整合性が保証され、呼び出し側で間違った種類のデータを渡そうとした瞬間にコンパイルエラーとして検出できます。複数の引数を持たせる場合は、カンマで区切って記述します。型名はコロンの後に記述し、誰が見ても一目で何が必要なデータかが分かるようになっています。
fn main() {
display_score("プレイヤーA", 85);
}
fn display_score(name: &str, score: i32) {
println!("{}様のスコアは {} 点です。", name, score);
}
プレイヤーA様のスコアは 85 点です。
3. 戻り値の定義と矢印記号の使い方
関数が何らかの計算や処理を行った結果を、呼び出し元に返す値を「戻り値(返り値)」と呼びます。Rustで戻り値を定義するには、引数リストの閉じ括弧の後に矢印記号(->)を書き、その後にデータの型を指定します。例えば、整数を返すなら-> i32、浮動小数点数なら-> f64といった具合です。
戻り値を指定した関数は、必ずその型の値を返さなければなりません。もし何も返さない関数であれば、戻り値の定義を省略します。この場合、厳密には「ユニット型」と呼ばれる空のデータ()が返されていることになりますが、通常は意識する必要はありません。関数のシグネチャを見るだけで、「何を渡して何が返ってくるのか」という情報の流れが完結しているのが、Rustの美しい設計の一部です。次に、この戻り値をどのようにして関数から送り出すのか、Rust独自の仕組みについて深く掘り下げていきましょう。
4. 文と式の違いを理解してRustらしく書く
Rustを習得する上で非常に重要なのが、「文(Statement)」と「式(Expression)」の区別です。この概念は他のプログラミング言語よりも厳格に扱われます。簡単に言うと、「文」は処理を行うだけで値を返さないもの(例:let x = 5;)であり、「式」は何らかの値を算出するものです。Rustの関数の本体は、最終的に一つの値を評価する「式」として機能させることができます。
Rustでは、関数の最後の一行にセミコロンを付けずに記述すると、その行が「式」として評価され、自動的に戻り値として扱われます。多くの言語ではreturnキーワードが必須ですが、Rustでは関数の終端で自然に値を流し出すような書き方が好まれます。もしセミコロンを付けてしまうと、それは「文」になり、戻り値がなくなってしまう(ユニット型になる)ため注意が必要です。この直感的な書き方に慣れると、コードの行数を減らしつつ、データの流れをスムーズに表現できるようになります。
fn main() {
let price = 1000;
let tax_included = add_tax(price);
println!("税込価格は {} 円です。", tax_included);
}
fn add_tax(amount: i32) -> i32 {
let tax_rate = 0.1;
// 最後にセミコロンを付けないことで、この計算結果が戻り値になる
(amount as f64 * (1.0 + tax_rate)) as i32
}
税込価格は 1100 円です。
5. returnキーワードによる早期リターンの活用
基本的には末尾の式で値を返しますが、特定の条件下で関数の処理を途中で切り上げたい場合には、returnキーワードを使用します。これを「早期リターン(Early Return)」と呼びます。例えば、入力値のバリデーションチェックを行い、不適切な値が渡された場合にすぐにエラー値を返して終了するようなケースで非常に有効です。
returnを使用する場合は、その後に値を記述し、文として完結させるためにセミコロンを付けます。早期リターンを適切に使うことで、関数のメインロジックが深いネスト(if文の重なりなど)の中に埋もれてしまうのを防ぎ、コードの可読性を飛躍的に高めることができます。Rustの開発者は、デフォルトでは末尾の式を使い、特殊な制御が必要なときだけreturnを使うという使い分けを行っています。
6. 複数の値を返すタプル型の戻り値
関数から二つ以上の値を返したい場面もよくあります。例えば、割り算の結果とその余りを同時に取得したい場合などです。多くの言語では、専用の構造体を作ったり、参照渡しを利用したりしますが、Rustでは「タプル(Tuple)」を使うことで簡単に複数の値を一つの戻り値としてまとめることができます。
戻り値の型定義に(i32, i32)のように記述し、関数内で複数の値を括弧で括って返します。呼び出し側では、「パターンマッチング」や「デストラクタ(解体)」を利用して、それぞれの値を個別の変数に展開して受け取ることが可能です。これにより、複雑なデータ構造を定義する手間を省きつつ、関連性の強い複数のデータを安全にやり取りできるようになります。
fn main() {
let (w, h) = get_dimensions();
println!("幅: {}, 高さ: {}", w, h);
}
fn get_dimensions() -> (u32, u32) {
let width = 1920;
let height = 1080;
(width, height) // タプルで複数の値を返す
}
幅: 1920, 高さ: 1080
7. 関数の引数における所有権と参照の基本
Rustの関数の挙動を理解する上で避けて通れないのが、「所有権」の概念です。関数に値を渡すとき、そのデータの種類によって「ムーブ(移動)」が発生するかどうかが決まります。整数などの単純な型(Copyトレイトを持つ型)は値がコピーされるため問題ありませんが、String型などの複雑なデータは、関数に渡した瞬間に所有権が関数内部へ移動してしまいます。
所有権が移動すると、呼び出し元の変数からはそのデータにアクセスできなくなります。これを防ぐために使われるのが「参照(Borrowing)」です。引数の型の前にアンパサンド(&)を付けることで、データの所有権を貸し出す形になり、関数終了後も呼び出し側でデータを使い続けることができます。関数の引数設計において、「この関数はデータを消費するのか、それとも一時的に閲覧するだけなのか」を判断することは、メモリ安全性を確保するための重要なステップです。
8. 高階関数とクロージャの入り口
Rustの関数は「第一級オブジェクト」に近い扱いが可能です。つまり、関数を変数に代入したり、別の関数の引数として渡したり、戻り値として関数を返したりすることができます。これを活用したのが「高階関数」です。特に、その場限りの匿名関数を定義する「クロージャ(Closure)」は、Rustの強力なイテレータ操作などで頻繁に登場します。
クロージャは通常の関数と異なり、周囲のスコープにある変数を「捕獲(キャプチャ)」できるという特徴があります。構文は|引数| { 処理 }という形式で、型推論が非常に強力に働くため、簡潔に記述できるのが魅力です。基本的な関数の書き方をマスターした後は、このクロージャや関数のポインタ概念を学ぶことで、Rustの表現力はさらに広がっていくことでしょう。まずは基本を確実に押さえ、安全で効率的なコードを書けるようになることが、Rustエンジニアへの第一歩です。