Rustの関数

Essential
最終更新: タグ: Rust

関数はRustの再利用可能なコードの基本単位だ。Zigを書いたことがあれば構文は馴染みやすいが、注釈と戻り値周りにいくつかの重要な違いがある。

関数の宣言

fn add(x: i32, y: i32) -> i32 {
    x + y
}

キーワードは fn だ。パラメータの型は常に必須——Rustはそれらを推論しない。戻り値の型は -> の後に続く。関数が何も返さない場合は -> 節を完全に省略する。Rustは暗黙的にユニット型(unit type)() を使う。

暗黙のリターン

関数本体はブロック式(block expression)だ。ブロック内の最後の式が戻り値になる——通常のケースでは return キーワードは不要だ。

fn double(n: i32) -> i32 {
    n * 2  // セミコロンなし → この式が戻り値
}

セミコロンを付けると式が**文(statement)**になり、値が捨てられる。するとブロックは () を返し、関数の宣言した戻り値の型が () でない場合は型エラーになる。

fn double_wrong(n: i32) -> i32 {
    n * 2;  // セミコロン: 値が捨てられ、ブロックは () を返す
    // コンパイルエラー: expected i32, found ()
}

この式と文の区別はRustで最初に内面化すべきことの1つだ。ルール: ブロックの末尾にセミコロンがない → その式が戻り値。

早期リターン

return は早期脱出に使える。

fn first_even(nums: &[i32]) -> Option<i32> {
    for &n in nums {
        if n % 2 == 0 {
            return Some(n);
        }
    }
    None // 最後の式、暗黙的に返される
}

慣用的なRustでは return は早期脱出のみに使い、最終値には使わない。

パラメータと所有権

各パラメータはRustの所有権ルールに従って引数を受け取る。T として宣言されたパラメータは値の所有権を取得し、&T は借用(読み取り専用)、&mut T はミュータブルに借用する。

fn length(s: &str) -> usize {
    s.len()
}

fn main() {
    let greeting = String::from("hello");
    let n = length(&greeting); // 呼び出し後も greeting は有効
    println!("{greeting} has {n} characters");
}

関数シグネチャは完全なコントラクトだ。呼び出し元に何がムーブまたは借用されるかを正確に伝える。

あらゆる場所のブロック式

ifmatch、そして普通のブロックはすべて式なので、関数本体として自然に組み合わせられる。

fn sign(n: i32) -> &'static str {
    if n > 0 { "positive" } else if n < 0 { "negative" } else { "zero" }
}

三項演算子(ternary operator)は不要だ——if 自体が値を生成する。

これにより、中間変数なしに複雑な値をインラインで計算できる。

fn clamp(x: f64, lo: f64, hi: f64) -> f64 {
    if x < lo { lo } else if x > hi { hi } else { x }
}

発散する関数

常にパニックし、永遠にループし、std::process::exit を呼ぶなどして決して返らない関数は、戻り値の型として !never型)を持つ。

fn fatal(msg: &str) -> ! {
    panic!("{msg}");
}

コンパイラは !matchif の任意のブランチで使えることを理解している。そのブランチは値をまったく生成しないからだ。