🔧

関数とメソッド — defがfnになると何が変わるか

戻り値の型、セミコロン、そしてRubyと同じ「最後の式が戻り値」ルール

Rubyでは関数定義で引数の型も戻り値の型も書かない。Rustは両方必須。

# Ruby
def greet(name)
  "Hello, #{name}!"
end

// Rust
fn greet(name: &str) -> String {
    format!("Hello, {}!", name)
}

->の後に戻り値の型を書く。戻り値なし(Rubyのnil返却)は-> ()だが省略可能。

セミコロン = 戻り値かどうか

RubyもRustも「最後の式が戻り値」というルールがある。ただしRustではセミコロンを付けるとstatementになり、戻り値ではなくなる。

fn add(a: i32, b: i32) -> i32 {
    a + b    // セミコロンなし → 戻り値
}

fn add_broken(a: i32, b: i32) -> i32 {
    a + b;   // セミコロンあり → statement、()を返す → コンパイルエラー!
}

これは最初本当に混乱する。Rubyではセミコロンは文の区切りでしかなく意味がないが、Rustでは戻り値の有無を決定する。

early return

Rubyと同じくreturnで早期リターンできる。

fn check_age(age: u32) -> &'static str {
    if age < 18 {
        return "minor";  // early returnにはセミコロン必要
    }
    "adult"  // 最後の式はセミコロンなし
}

returnにはセミコロンを付ける。最後の式での戻りには付けない。これがRustの慣例。

引数の渡し方 — 値 vs 参照

Rubyでは引数の渡し方を気にする必要がない。Rustでは所有権の問題で借用するか(borrow)、渡すか(move)を決定する必要がある。

// 借用 — 元のデータ維持
fn print_name(name: &str) {
    println!("{}", name);
}

// 所有権移動 — 呼び出し後に元のデータ使用不可
fn take_name(name: String) {
    println!("{}", name);
}

let name = String::from("sehwa");
print_name(&name);  // 借用、nameはまだ使える
take_name(name);     // 移動、以降nameは使用不可

大抵の場合&で借用すればいい。「読むだけなのに所有権まで渡す必要はない」という判断。

クロージャ — インライン関数

Rubyのブロック/lambdaがRustではクロージャ。関数の引数として渡すパターンがほぼ同じ。

# Ruby
[1, 2, 3].map { |x| x * 2 }

// Rust
vec![1, 2, 3].iter().map(|x| x * 2).collect::<Vec<_>>();

|x|がRubyの{ |x| }に対応する。波括弧の代わりにパイプ(|)で引数を囲む。

キーポイント

1

fn 名前(引数: 型) -> 戻り値型 { } の形式で定義

2

セミコロンなし = 戻り値、あり = statement — これが核心

3

early returnにはreturnキーワード + セミコロンを使用

4

引数は&で借用するか所有権を移動する

メリット

  • 関数シグネチャだけで入出力の型がわかる
  • Rubyと同じ「最後の式が戻り値」ルールで適応が速い

デメリット

  • セミコロンのミスでコンパイルエラーが出るのが初期に頻発する
  • 借用/所有権の決定がRubyにない概念なので関数設計が難しい

ユースケース

Rubyのメソッドチェーンをrust関数の組み合わせに変換する時 コールバックパターンをクロージャで実装する時

参考資料