快轉到主要內容
  1. 教學文章/

Rust Closures + Iterators:用 FP 風格寫出優雅的 Rust

·9 分鐘· loading · loading · ·
Rust Python Closures Iterators Functional Programming
每日拍拍
作者
每日拍拍
科學家 X 科技宅宅
目錄
Rust 入門 - 本文屬於一個選集。
§ 9: 本文

一、前言
#

如果你寫過 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 有 lambdamapfilter,還有更 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 被呼叫,型別就固定了。你不能同時對 i32f64 用同一個 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 是 FnFnMut 還是 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 裡用過 mapfilter、list comprehension,那轉到 Rust 的 iterator chain 應該會很自然!

下一篇我們要來看 Rust 的智慧指標(Box、Rc、Arc),繼續深入 Rust 的記憶體管理世界~ 🦀

延伸閱讀
#

Rust 入門 - 本文屬於一個選集。
§ 9: 本文

相關文章

Rust Collections vs Python:Vec 和 HashMap 的生存指南
·7 分鐘· loading · loading
Rust Python Collections Vec Hashmap
Rust 生命週期(Lifetime)入門:編譯器教你管記憶體
·8 分鐘· loading · loading
Rust Lifetime Borrowing Memory-Safety Python
Rust 錯誤處理:Result/Option vs Python try/except
·7 分鐘· loading · loading
Rust Python Error-Handling Result Option
Rust for Python 開發者:Ownership 與 Borrowing 入門
·7 分鐘· loading · loading
Rust Python Ownership Borrowing Memory
Rust CLI 實戰:用 clap 打造命令列工具(Python Typer 對照版)
·5 分鐘· loading · loading
Rust Cli Clap Typer Python
Rust Ownership & Borrowing:跟 Python GC 說再見
·8 分鐘· loading · loading
Rust Ownership Borrowing Python 記憶體管理