一、前言 #
如果你寫過 Python,一定對這種寫法不陌生:
numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
result = list(map(lambda x: x ** 2, filter(lambda x: x % 2 == 0, numbers)))
# [4, 16, 36, 64, 100]
Python 有 lambda、map、filter,還有更 Pythonic 的 list comprehension。那 Rust 呢?
Rust 不但有 closures(閉包)和 iterators(迭代器),而且它的函數式程式設計(FP)體驗可以說是比 Python 更優雅——零成本抽象、類型安全、惰性求值全部內建。
今天拍拍君就帶你從 Python 的 FP 經驗出發,徹底搞懂 Rust 的 closures 和 iterators!
二、Closures:Rust 版的 Lambda #
Python 的 Lambda #
Python 的 lambda 很簡單,但只能寫一行表達式:
add = lambda x, y: x + y
print(add(3, 5)) # 8
# 捕捉外部變數
multiplier = 3
triple = lambda x: x * multiplier
print(triple(10)) # 30
Rust 的 Closure #
Rust 的 closure 用 |參數| 語法,功能比 Python lambda 強大得多:
fn main() {
// 基本 closure
let add = |x: i32, y: i32| x + y;
println!("{}", add(3, 5)); // 8
// 捕捉外部變數
let multiplier = 3;
let triple = |x: i32| x * multiplier;
println!("{}", triple(10)); // 30
// 多行 closure(Python lambda 做不到!)
let process = |x: i32| {
let doubled = x * 2;
let message = format!("結果是 {}", doubled);
message
};
println!("{}", process(21)); // 結果是 42
}
💡 差異重點:Rust closure 可以寫多行邏輯,Python lambda 只能一行。
型別推導 #
Rust 的 closure 大部分時候不需要標注型別,編譯器會自動推導:
let add = |x, y| x + y; // 編譯器推導型別
let result = add(3, 5); // 推導出 i32
println!("{}", result); // 8
但注意:一旦 closure 被呼叫,型別就固定了。你不能同時對 i32 和 f64 用同一個 closure:
let add = |x, y| x + y;
let a = add(3, 5); // 固定為 i32
// let b = add(3.0, 5.0); // ❌ 編譯錯誤!已經是 i32 了
三、Closure 的三種 Trait #
這是 Rust closure 最特別的地方——編譯器根據 closure 怎麼使用捕捉的變數,自動為它實作不同的 trait:
| Trait | 捕捉方式 | Python 類比 | 能力 |
|---|---|---|---|
Fn |
不可變借用 &T |
一般 lambda | 可重複呼叫 |
FnMut |
可變借用 &mut T |
會修改外部狀態的函數 | 可重複呼叫,但會改值 |
FnOnce |
取得所有權 T |
— | 只能呼叫一次 |
來看實際範例:
fn main() {
// Fn:只讀取外部變數
let name = String::from("拍拍君");
let greet = || println!("你好,{}!", name);
greet(); // 可以呼叫多次
greet(); // ✅ 沒問題
// FnMut:修改外部變數
let mut count = 0;
let mut increment = || {
count += 1;
println!("計數:{}", count);
};
increment(); // 計數:1
increment(); // 計數:2
// FnOnce:消耗外部變數
let data = vec![1, 2, 3];
let consume = || {
let moved = data; // 取得 data 的所有權
println!("消耗了:{:?}", moved);
};
consume(); // ✅ 第一次 OK
// consume(); // ❌ 編譯錯誤!data 已經被 move 了
}
Python 不需要擔心這些,因為有 GC。但 Rust 的這套系統讓編譯器能在編譯期就保證記憶體安全,不需要任何執行期開銷。
move 關鍵字
#
有時候你想強制 closure 取得所有權(例如要把 closure 傳到另一個 thread):
use std::thread;
fn main() {
let message = String::from("Hello from 拍拍君!");
// move 強制 closure 取得 message 的所有權
let handle = thread::spawn(move || {
println!("{}", message);
});
// println!("{}", message); // ❌ message 已經 move 了
handle.join().unwrap();
}
四、Iterators:Rust 的鏈式 FP 利器 #
Python 的做法 #
Python 有好幾種 FP 風格的寫法:
numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
# 方法 1:map + filter
result = list(map(lambda x: x ** 2, filter(lambda x: x % 2 == 0, numbers)))
# 方法 2:list comprehension(更 Pythonic)
result = [x ** 2 for x in numbers if x % 2 == 0]
# [4, 16, 36, 64, 100]
Rust 的 Iterator Chain #
Rust 用 iterator 搭配 closure,寫出來的程式碼又優雅又高效:
fn main() {
let numbers = vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
let result: Vec<i32> = numbers
.iter()
.filter(|&&x| x % 2 == 0) // 篩選偶數
.map(|&x| x * x) // 平方
.collect(); // 收集結果
println!("{:?}", result); // [4, 16, 36, 64, 100]
}
注意到了嗎?Rust 的 iterator chain 是從左到右、從上到下閱讀的,比 Python 的 map(lambda, filter(lambda, ...)) 好讀多了!
惰性求值(Lazy Evaluation) #
Rust 的 iterator 是惰性的——在你呼叫 .collect()、.sum() 等「消費者」方法之前,什麼計算都不會發生:
fn main() {
let numbers = vec![1, 2, 3, 4, 5];
// 這行不會做任何計算!
let lazy_iter = numbers.iter().map(|x| {
println!("處理 {}", x);
x * 2
});
println!("還沒開始計算...");
// 現在才真的開始
let result: Vec<_> = lazy_iter.collect();
println!("{:?}", result);
}
輸出:
還沒開始計算...
處理 1
處理 2
處理 3
處理 4
處理 5
[2, 4, 6, 8, 10]
Python 的 map() 和 filter() 其實也是惰性的,但大家都習慣直接用 list comprehension,就失去了惰性求值的好處。
五、常用 Iterator 方法大全 #
Rust 的 Iterator trait 提供了豐富的方法,以下是最常用的:
轉換類 #
fn main() {
let words = vec!["hello", "world", "rust"];
// map:一對一轉換
let upper: Vec<String> = words.iter().map(|w| w.to_uppercase()).collect();
println!("{:?}", upper); // ["HELLO", "WORLD", "RUST"]
// flat_map:一對多轉換(攤平)
let chars: Vec<char> = words.iter().flat_map(|w| w.chars()).collect();
println!("{:?}", chars); // ['h', 'e', 'l', 'l', 'o', 'w', 'o', 'r', 'l', 'd', ...]
// enumerate:加上索引(像 Python 的 enumerate)
for (i, word) in words.iter().enumerate() {
println!("{}: {}", i, word);
}
// zip:配對兩個 iterator(像 Python 的 zip)
let numbers = vec![1, 2, 3];
let pairs: Vec<_> = words.iter().zip(numbers.iter()).collect();
println!("{:?}", pairs); // [("hello", 1), ("world", 2), ("rust", 3)]
}
篩選類 #
fn main() {
let numbers = vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
// filter:保留符合條件的
let evens: Vec<_> = numbers.iter().filter(|&&x| x > 5).collect();
println!("{:?}", evens); // [6, 7, 8, 9, 10]
// take / skip:取前 N 個 / 跳過前 N 個
let first_three: Vec<_> = numbers.iter().take(3).collect();
let after_three: Vec<_> = numbers.iter().skip(3).collect();
println!("{:?}", first_three); // [1, 2, 3]
println!("{:?}", after_three); // [4, 5, 6, 7, 8, 9, 10]
// take_while / skip_while:條件式取/跳
let small: Vec<_> = numbers.iter().take_while(|&&x| x < 5).collect();
println!("{:?}", small); // [1, 2, 3, 4]
}
聚合類(消費者) #
fn main() {
let numbers = vec![1, 2, 3, 4, 5];
// sum:加總
let total: i32 = numbers.iter().sum();
println!("總和:{}", total); // 15
// count:計數
let even_count = numbers.iter().filter(|&&x| x % 2 == 0).count();
println!("偶數個數:{}", even_count); // 2
// any / all:存在/全部檢查
let has_big = numbers.iter().any(|&x| x > 3);
let all_positive = numbers.iter().all(|&x| x > 0);
println!("有大於 3 的?{}", has_big); // true
println!("全部都正?{}", all_positive); // true
// min / max
println!("最小:{:?}", numbers.iter().min()); // Some(1)
println!("最大:{:?}", numbers.iter().max()); // Some(5)
// find:找第一個符合條件的
let first_even = numbers.iter().find(|&&x| x % 2 == 0);
println!("第一個偶數:{:?}", first_even); // Some(2)
}
fold:最強大的聚合
#
fold 類似 Python 的 functools.reduce,可以做任何聚合操作:
fn main() {
let numbers = vec![1, 2, 3, 4, 5];
// 用 fold 做加總(等同 .sum())
let total = numbers.iter().fold(0, |acc, &x| acc + x);
println!("fold 加總:{}", total); // 15
// 用 fold 做更複雜的事:建構字串
let sentence = numbers.iter().fold(String::new(), |acc, &x| {
if acc.is_empty() {
format!("{}", x)
} else {
format!("{}, {}", acc, x)
}
});
println!("{}", sentence); // 1, 2, 3, 4, 5
}
Python 對比:
from functools import reduce
numbers = [1, 2, 3, 4, 5]
total = reduce(lambda acc, x: acc + x, numbers, 0) # 15
六、實戰範例:資料處理 Pipeline #
來看一個完整的實戰範例——處理一批使用者資料:
#[derive(Debug)]
struct User {
name: String,
age: u32,
score: f64,
}
fn main() {
let users = vec![
User { name: "拍拍君".into(), age: 25, score: 88.5 },
User { name: "chatPTT".into(), age: 30, score: 72.0 },
User { name: "拍拍醬".into(), age: 22, score: 95.0 },
User { name: "Ferris".into(), age: 28, score: 60.5 },
User { name: "PyPy".into(), age: 35, score: 91.0 },
];
// 找出所有 30 歲以下、分數大於 80 的使用者名字
let honors: Vec<&str> = users
.iter()
.filter(|u| u.age < 30 && u.score > 80.0)
.map(|u| u.name.as_str())
.collect();
println!("榮譽榜:{:?}", honors);
// ["拍拍君", "拍拍醬"]
// 計算平均分數
let avg_score: f64 = users.iter().map(|u| u.score).sum::<f64>()
/ users.len() as f64;
println!("平均分數:{:.1}", avg_score);
// 81.4
// 找分數最高的使用者
let top_user = users
.iter()
.max_by(|a, b| a.score.partial_cmp(&b.score).unwrap());
println!("最高分:{:?}", top_user.map(|u| &u.name));
// Some("拍拍醬")
// 按年齡分組統計
let (young, senior): (Vec<_>, Vec<_>) = users
.iter()
.partition(|u| u.age < 30);
println!("年輕組 ({} 人):{:?}", young.len(),
young.iter().map(|u| &u.name).collect::<Vec<_>>());
println!("資深組 ({} 人):{:?}", senior.len(),
senior.iter().map(|u| &u.name).collect::<Vec<_>>());
}
這段如果用 Python 寫大概會長這樣:
# Python 版本
honors = [u.name for u in users if u.age < 30 and u.score > 80.0]
avg_score = sum(u.score for u in users) / len(users)
top_user = max(users, key=lambda u: u.score)
young = [u for u in users if u.age < 30]
senior = [u for u in users if u.age >= 30]
Python 確實更簡潔,但 Rust 版本的好處是:編譯期保證型別安全 + 零成本抽象(跟手寫 for loop 一樣快)。
七、自訂 Iterator #
在 Python 裡,你可以用 generator 輕鬆建立 iterator:
def fibonacci():
a, b = 0, 1
while True:
yield a
a, b = b, a + b
fib = fibonacci()
print([next(fib) for _ in range(10)])
# [0, 1, 1, 2, 3, 5, 8, 13, 21, 34]
Rust 沒有 yield(stable 版),但可以透過實作 Iterator trait 達到同樣效果:
struct Fibonacci {
a: u64,
b: u64,
}
impl Fibonacci {
fn new() -> Self {
Fibonacci { a: 0, b: 1 }
}
}
impl Iterator for Fibonacci {
type Item = u64;
fn next(&mut self) -> Option<Self::Item> {
let current = self.a;
let new_b = self.a + self.b;
self.a = self.b;
self.b = new_b;
Some(current)
}
}
fn main() {
let fib_10: Vec<u64> = Fibonacci::new().take(10).collect();
println!("{:?}", fib_10);
// [0, 1, 1, 2, 3, 5, 8, 13, 21, 34]
// 還可以直接接 iterator chain!
let even_fibs: Vec<u64> = Fibonacci::new()
.take(20)
.filter(|&x| x % 2 == 0)
.collect();
println!("偶數費氏數列:{:?}", even_fibs);
// [0, 2, 8, 34, 144, 2584]
}
雖然比 Python 囉嗦一點,但自訂 iterator 可以完美融入整個 iterator chain 生態系,組合起來非常強大。
八、Closure 作為函式參數 #
在實際開發中,closure 最常見的用途是作為函式參數:
// 接受任何 Fn closure 的函式
fn apply_twice<F: Fn(i32) -> i32>(f: F, x: i32) -> i32 {
f(f(x))
}
// 接受 FnMut 的函式
fn apply_n_times<F: FnMut()>(mut f: F, n: usize) {
for _ in 0..n {
f();
}
}
fn main() {
let double = |x| x * 2;
println!("apply_twice(double, 3) = {}", apply_twice(double, 3));
// 12(先 3*2=6,再 6*2=12)
let add_one = |x| x + 1;
println!("apply_twice(add_one, 10) = {}", apply_twice(add_one, 10));
// 12(先 10+1=11,再 11+1=12)
let mut total = 0;
apply_n_times(|| { total += 1; }, 5);
println!("total = {}", total); // 5
}
Python 對比:
def apply_twice(f, x):
return f(f(x))
print(apply_twice(lambda x: x * 2, 3)) # 12
Python 不需要指定 closure 的 trait,因為它是動態語言。Rust 需要你告訴編譯器:「這個 closure 是 Fn、FnMut 還是 FnOnce?」——但好處是零成本抽象,編譯器會把 closure 直接內聯(inline),不需要 function pointer。
九、速度比較:Iterator vs For Loop #
你可能擔心:iterator chain 這麼花俏,會不會比手寫 for loop 慢?
答案是:完全不會! Rust 的 iterator 是零成本抽象,編譯後的機器碼跟手寫 for loop 一模一樣。
// 這兩個產生一樣的機器碼:
// 方法 1:Iterator chain
let sum: i32 = (0..1000).filter(|x| x % 2 == 0).sum();
// 方法 2:手寫 for loop
let mut sum = 0;
for x in 0..1000 {
if x % 2 == 0 {
sum += x;
}
}
事實上,在某些情況下 iterator 甚至更快,因為編譯器更容易對 iterator chain 做 SIMD 向量化優化!
結語 #
今天我們學了 Rust 的兩大 FP 利器:
| 概念 | Python | Rust |
|---|---|---|
| 匿名函式 | lambda(限一行) |
closure |x|(多行 OK) |
| 捕捉機制 | GC 管理 | Fn / FnMut / FnOnce |
| 強制取得所有權 | 不需要 | move 關鍵字 |
| 鏈式操作 | map/filter(或 comprehension) |
.iter().map().filter().collect() |
| 惰性求值 | map()/filter() 本身惰性 |
Iterator 全部惰性 |
| 自訂 iterator | yield generator |
實作 Iterator trait |
| 效能 | 有 GC 開銷 | 零成本抽象 |
Rust 的 iterator + closure 組合拳,讓你可以用 FP 風格寫出既優雅又高效的程式碼。如果你之前在 Python 裡用過 map、filter、list comprehension,那轉到 Rust 的 iterator chain 應該會很自然!
下一篇我們要來看 Rust 的智慧指標(Box、Rc、Arc),繼續深入 Rust 的記憶體管理世界~ 🦀
延伸閱讀 #
- 📖 The Rust Book - Closures
- 📖 The Rust Book - Iterators
- 📖 Iterator trait 文件
- 🦀 Rust Ownership 與 Borrowing — 拍拍君帶你搞懂所有權
- 🦀 Rust Collections — Vec 和 HashMap 的生存指南