Skip to content

建立 2026-09-19 更新 2026-09-19

閉包與迭代器

閉包(closure)是可以捕獲周圍變數的匿名函式;迭代器(iterator)則是處理一連串資料的抽象。兩者搭配,可以用 mapfilter 這類串接的寫法取代手寫迴圈,而且效能相同。這是 Rust 中很有代表性的「高階抽象、零成本」範例。

閉包

語法是 |參數| 運算式,參數與回傳型別通常可以省略,由編譯器推斷:

fn main() {
    let add_one = |x: i32| x + 1;
    let square = |x| x * x;
    let greet = |name: &str| {
        let msg = format!("哈囉,{name}!");
        msg
    };

    println!("{} {} {}", add_one(5), square(4), greet("Rust"));
}

捕獲環境

閉包能直接使用外部的變數,這是它與一般函式最大的差別:

fn main() {
    let threshold = 10;
    let is_big = |n: i32| n > threshold; // 借用 threshold
    println!("{} {}", is_big(5), is_big(20));
}

閉包會依需求自動選擇最省的捕獲方式:唯讀借用、可修改借用,或取走所有權。若要強制取走所有權,在前面加上 move,常用於把閉包交給另一個執行緒:

fn main() {
    let name = String::from("Rust");
    let own = move || println!("擁有 {name}");
    own();
    // println!("{name}"); // 編譯錯誤:name 已經被移進閉包
}

三種閉包 trait

編譯器依閉包如何使用捕獲的值,自動為它實作以下 trait,函式參數要接收閉包時會用到:

Trait 說明
FnOnce 只能呼叫一次(會取走捕獲的值)
FnMut 可以多次呼叫,且會修改捕獲的值
Fn 可以多次呼叫,只讀取捕獲的值
fn apply<F: Fn(i32) -> i32>(f: F, value: i32) -> i32 {
    f(value)
}

fn main() {
    let factor = 3;
    println!("{}", apply(|x| x * factor, 7));
}

迭代器

迭代器是實作了 Iterator trait 的型別,核心是 next() 方法。for 迴圈背後就是不斷呼叫 next()。取得迭代器的三種常見方式:

方法 產生的元素 說明
.iter() &T 唯讀借用,集合之後仍可使用
.iter_mut() &mut T 可修改借用
.into_iter() T 取走所有權,集合之後不能使用

迭代器是惰性(lazy)的:串接 mapfilter 這類轉接器(adaptor)本身不會做任何事,要等到呼叫 collectsumfor 這類消費者(consumer)才會真正執行。

常用操作

fn main() {
    let nums = vec![1, 2, 3, 4, 5, 6];

    // filter 留下偶數,map 求平方,collect 收集成 Vec
    let result: Vec<i32> = nums
        .iter()
        .filter(|&&n| n % 2 == 0)
        .map(|n| n * n)
        .collect();
    println!("{:?}", result);

    // 直接算總和與計數
    let sum: i32 = nums.iter().sum();
    let count = nums.iter().filter(|&&n| n > 3).count();
    println!("{sum} {count}");

    // 加上索引、兩個序列配對
    for (i, n) in nums.iter().enumerate().take(2) {
        println!("{i}: {n}");
    }
    let names = ["甲", "乙", "丙"];
    for (name, n) in names.iter().zip(nums.iter()) {
        println!("{name} = {n}");
    }

    // 尋找、判斷
    println!("{:?}", nums.iter().find(|&&n| n > 4));
    println!("{}", nums.iter().any(|&n| n == 3));
    println!("{}", nums.iter().all(|&n| n > 0));
}

collect 可以收集成不同型別

collect 依你指定的型別決定輸出,例如收集成 HashMapString

use std::collections::HashMap;

fn main() {
    let words = ["apple", "fig", "banana"];

    let lengths: HashMap<&str, usize> = words.iter().map(|w| (*w, w.len())).collect();
    println!("{}", lengths["fig"]);

    let shout: String = words.iter().map(|w| w.to_uppercase()).collect::<Vec<_>>().join("-");
    println!("{shout}");
}

自訂迭代器

只要實作 next(),你的型別就能使用所有迭代器方法:

struct Countdown(u32);

impl Iterator for Countdown {
    type Item = u32;

    fn next(&mut self) -> Option<u32> {
        if self.0 == 0 {
            None
        } else {
            self.0 -= 1;
            Some(self.0 + 1)
        }
    }
}

fn main() {
    let items: Vec<u32> = Countdown(3).collect();
    println!("{:?}", items);

    let total: u32 = Countdown(4).map(|n| n * 10).sum();
    println!("{total}");
}

迭代器會比手寫迴圈慢嗎?

不會。迭代器鏈在編譯時會被展開、內聯並最佳化,效能與手寫的 for 迴圈相當,這就是所謂的「零成本抽象」。可讀性通常更好,也不會出現索引越界。

推薦影音

閉包

簡述:Let's Get Rusty 依照官方書籍第 13 章製作,說明閉包的語法、捕獲方式與三種閉包 trait。

迭代器

簡述:同一系列的迭代器單元,介紹 Iterator trait、轉接器與消費者,對應本頁「迭代器」一節。

想更深入的話,可以到 影音教學總覽 看 Jon Gjengset 的《Crust of Rust: Iterators》。

接下來前往 進階主題