第9章: 関数とクロージャ
学習目標
- 関数の定義と呼び出しを理解する
- クロージャの構文と使い方を学ぶ
- 環境のキャプチャ方法を把握する
- イテレータとクロージャの組み合わせを習得する
---
9.1 関数の基本
9.1.1 関数の定義
fn add(a: i32, b: i32) -> i32 {
a + b
}
fn main() {
let result = add(5, 3);
println!("結果: {}", result);
}
構文:
fn 関数名(引数名: 型, ...) -> 戻り値の型 {
処理
}
9.1.2 戻り値
return キーワード:
fn is_even(n: i32) -> bool {
if n % 2 == 0 {
return true;
}
false
}
式としての戻り値(推奨):
fn is_even(n: i32) -> bool {
n % 2 == 0
}
戻り値なし:
fn greet(name: &str) {
println!("Hello, {}!", name);
}
// 明示的なunit型
fn greet_explicit(name: &str) -> () {
println!("Hello, {}!", name);
}
9.1.3 複数の戻り値
タプルを使用:
fn divide(a: i32, b: i32) -> (i32, i32) {
(a / b, a % b)
}
fn main() {
let (quotient, remainder) = divide(10, 3);
println!("{} ÷ {} = {} 余り {}", 10, 3, quotient, remainder);
}
Result型を使用:
fn divide_safe(a: i32, b: i32) -> Result<i32, String> {
if b == 0 {
Err(String::from("ゼロ除算エラー"))
} else {
Ok(a / b)
}
}
fn main() {
match divide_safe(10, 0) {
Ok(result) => println!("結果: {}", result),
Err(e) => println!("エラー: {}", e),
}
}
---
9.2 関数のパラメータ
9.2.1 値渡し
fn change_value(mut x: i32) {
x = 10;
println!("関数内: {}", x); // 10
}
fn main() {
let x = 5;
change_value(x);
println!("関数外: {}", x); // 5(変更されない)
}
9.2.2 参照渡し
不変参照:
fn print_length(s: &String) {
println!("長さ: {}", s.len());
}
fn main() {
let text = String::from("hello");
print_length(&text);
println!("{}", text); // textはまだ使える
}
可変参照:
fn append_world(s: &mut String) {
s.push_str(", world");
}
fn main() {
let mut text = String::from("hello");
append_world(&mut text);
println!("{}", text); // "hello, world"
}
9.2.3 所有権の移動
fn take_ownership(s: String) {
println!("{}", s);
} // sがここでドロップ
fn main() {
let text = String::from("hello");
take_ownership(text);
// println!("{}", text); // ❌ エラー!textはムーブ済み
}
---
9.3 クロージャの基本
9.3.1 クロージャとは
無名関数とも呼ばれ、環境をキャプチャできます:
fn main() {
let add = |a, b| a + b;
let result = add(5, 3);
println!("結果: {}", result); // 8
}
構文の比較:
// 関数
fn add_fn(a: i32, b: i32) -> i32 { a + b }
// クロージャ(型注釈あり)
let add_closure = |a: i32, b: i32| -> i32 { a + b };
// クロージャ(型推論)
let add_closure = |a, b| a + b;
// クロージャ(複数行)
let add_closure = |a, b| {
let result = a + b;
result
};
9.3.2 環境のキャプチャ
クロージャは外側のスコープの変数をキャプチャできます:
fn main() {
let x = 10;
let print_x = || println!("x = {}", x);
print_x(); // x = 10
}
関数との違い:
fn main() {
let x = 10;
// ❌ 関数は環境をキャプチャできない
// fn print_x() {
// println!("x = {}", x);
// }
// ✅ クロージャはキャプチャできる
let print_x = || println!("x = {}", x);
print_x();
}
9.3.3 キャプチャの3つの方法
1. 不変借用(&T):
fn main() {
let list = vec![1, 2, 3];
let print_list = || println!("{:?}", list);
print_list();
println!("{:?}", list); // ✅ listはまだ使える
}
2. 可変借用(&mut T):
fn main() {
let mut list = vec![1, 2, 3];
let mut add_item = || list.push(4);
add_item();
// println!("{:?}", list); // ❌ エラー(可変借用中)
}
3. 所有権の移動(move):
fn main() {
let list = vec![1, 2, 3];
let print_list = move || println!("{:?}", list);
print_list();
// println!("{:?}", list); // ❌ エラー!listはムーブ済み
}
moveキーワード:
use std::thread;
fn main() {
let list = vec![1, 2, 3];
// スレッドで使用するには所有権を移動
thread::spawn(move || {
println!("{:?}", list);
}).join().unwrap();
// println!("{:?}", list); // ❌ ムーブ済み
}
---
9.4 クロージャの型
9.4.1 Fn トレイト
3つのトレイトが自動実装されます:
┌─────────────────────────────────────────────────────────┐
│ クロージャのトレイト │
├─────────────────────────────────────────────────────────┤
│ │
│ FnOnce │
│ - 環境を消費する │
│ - 1回しか呼べない │
│ │
│ FnMut │
│ - 環境を可変借用 │
│ - 複数回呼べる │
│ - 環境を変更できる │
│ │
│ Fn │
│ - 環境を不変借用 │
│ - 複数回呼べる │
│ - 環境を変更しない │
│ │
│ 継承関係: Fn ⊂ FnMut ⊂ FnOnce │
│ │
└─────────────────────────────────────────────────────────┘
例:
fn apply<F>(f: F) where F: Fn(i32) -> i32 {
let result = f(5);
println!("結果: {}", result);
}
fn main() {
let double = |x| x * 2;
apply(double);
}
9.4.2 FnOnce
fn consume<F>(f: F) where F: FnOnce() {
f();
// f(); // ❌ エラー!FnOnceは1回しか呼べない
}
fn main() {
let s = String::from("hello");
let consume_string = || {
let _moved = s; // 所有権を移動
};
consume(consume_string);
}
9.4.3 FnMut
fn call_twice<F>(mut f: F) where F: FnMut() {
f();
f();
}
fn main() {
let mut count = 0;
let mut increment = || {
count += 1;
println!("count = {}", count);
};
call_twice(increment);
}
---
9.5 イテレータとクロージャ
9.5.1 map
各要素を変換:
fn main() {
let numbers = vec![1, 2, 3, 4, 5];
let doubled: Vec<i32> = numbers
.iter()
.map(|x| x * 2)
.collect();
println!("{:?}", doubled); // [2, 4, 6, 8, 10]
}
9.5.2 filter
条件に合う要素のみ抽出:
fn main() {
let numbers = vec![1, 2, 3, 4, 5, 6];
let evens: Vec<i32> = numbers
.iter()
.filter(|&x| x % 2 == 0)
.copied()
.collect();
println!("{:?}", evens); // [2, 4, 6]
}
9.5.3 fold
累積計算:
fn main() {
let numbers = vec![1, 2, 3, 4, 5];
let sum = numbers
.iter()
.fold(0, |acc, &x| acc + x);
println!("合計: {}", sum); // 15
}
9.5.4 チェーンの組み合わせ
fn main() {
let numbers = vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
let result: i32 = numbers
.iter()
.filter(|&x| x % 2 == 0) // 偶数のみ
.map(|x| x * x) // 二乗
.sum(); // 合計
println!("結果: {}", result); // 220
}
---
9.6 高階関数
9.6.1 関数を引数に取る
fn apply_twice<F>(f: F, x: i32) -> i32
where
F: Fn(i32) -> i32,
{
f(f(x))
}
fn main() {
let double = |x| x * 2;
let result = apply_twice(double, 5);
println!("結果: {}", result); // 20
}
9.6.2 関数を返す
fn make_adder(n: i32) -> impl Fn(i32) -> i32 {
move |x| x + n
}
fn main() {
let add_5 = make_adder(5);
let add_10 = make_adder(10);
println!("{}", add_5(3)); // 8
println!("{}", add_10(3)); // 13
}
9.6.3 カリー化
fn curry_add(a: i32) -> impl Fn(i32) -> i32 {
move |b| a + b
}
fn main() {
let add_5 = curry_add(5);
println!("{}", add_5(10)); // 15
println!("{}", add_5(20)); // 25
}
---
9.7 実践例
例1:コレクション処理
#[derive(Debug)]
struct Person {
name: String,
age: u32,
}
fn main() {
let people = vec![
Person { name: "Alice".to_string(), age: 30 },
Person { name: "Bob".to_string(), age: 25 },
Person { name: "Charlie".to_string(), age: 35 },
];
// 30歳以上の人の名前を取得
let names: Vec<&str> = people
.iter()
.filter(|p| p.age >= 30)
.map(|p| p.name.as_str())
.collect();
println!("{:?}", names); // ["Alice", "Charlie"]
}
例2:エラーハンドリング
fn parse_numbers(input: &str) -> Result<Vec<i32>, std::num::ParseIntError> {
input
.split_whitespace()
.map(|s| s.parse::<i32>())
.collect()
}
fn main() {
match parse_numbers("1 2 3 4 5") {
Ok(numbers) => println!("{:?}", numbers),
Err(e) => println!("エラー: {}", e),
}
}
例3:設定オブジェクト
struct Config {
threshold: i32,
}
impl Config {
fn new(threshold: i32) -> Self {
Config { threshold }
}
fn filter_below(&self) -> impl Fn(&i32) -> bool + '_ {
move |&x| x >= self.threshold
}
}
fn main() {
let config = Config::new(5);
let numbers = vec![1, 3, 5, 7, 9];
let filtered: Vec<i32> = numbers
.iter()
.filter(config.filter_below())
.copied()
.collect();
println!("{:?}", filtered); // [5, 7, 9]
}
例4:遅延評価
fn main() {
let numbers = vec![1, 2, 3, 4, 5];
// イテレータは遅延評価
let iter = numbers
.iter()
.map(|x| {
println!("処理中: {}", x);
x * 2
});
println!("イテレータ作成完了");
// collectで初めて実行される
let result: Vec<i32> = iter.collect();
println!("{:?}", result);
}
---
9.8 パフォーマンスの考慮
9.8.1 ゼロコスト抽象化
クロージャとイテレータはゼロコスト抽象化:
// ループ版
let mut sum = 0;
for i in 0..10 {
sum += i;
}
// イテレータ版(同じ速さ)
let sum: i32 = (0..10).sum();
コンパイル後、両方とも同じ機械語に:
┌─────────────────────────────────────────────────────────┐
│ ゼロコスト抽象化の例 │
├─────────────────────────────────────────────────────────┤
│ │
│ 高レベル(Rustコード) │
│ numbers.iter().map(|x| x * 2).sum() │
│ │
│ ↓ コンパイル │
│ │
│ 低レベル(機械語) │
│ 最適化されたループ │
│ - インライン化 │
│ - ベクトル化 │
│ - オーバーヘッドなし │
│ │
└─────────────────────────────────────────────────────────┘
9.8.2 イテレータ vs ループ
イテレータの利点:
// ✅ 読みやすい
let sum: i32 = numbers.iter().sum();
// ✅ 安全(境界チェック不要)
let evens: Vec<i32> = numbers
.iter()
.filter(|&x| x % 2 == 0)
.copied()
.collect();
// ✅ 並列化が容易(rayon クレート)
use rayon::prelude::*;
let sum: i32 = numbers.par_iter().sum();
---
9.9 関数ポインタ
9.9.1 fn型
fn add(a: i32, b: i32) -> i32 {
a + b
}
fn do_twice(f: fn(i32, i32) -> i32, arg1: i32, arg2: i32) -> i32 {
f(arg1, arg2) + f(arg1, arg2)
}
fn main() {
let result = do_twice(add, 5, 3);
println!("{}", result); // 16
}
fnとFnの違い:
fn → 関数ポインタ(環境なし)
Fn → クロージャトレイト(環境あり)
---
9.10 まとめ
学んだこと
- 関数の基本
- クロージャ
- イテレータ
- 高階関数
次のステップ
次の章では、構造体を学びます:
- 構造体の定義
- メソッドと関連関数
- implブロック
- タプル構造体とユニット構造体
- The Rust Programming Language - Chapter 13
- Rust By Example - Closures
- Iterator Documentation
---