嗨嗨!大家好!歡迎來到 Rust 三十天挑戰的第十五天!
恭喜你!我們已經順利完成了前兩週的基礎學習,從 Rust 的語法基礎到測試框架,從所有權系統到智慧指標。今天我們要開始第三週的學習旅程,探討一個讓 Rust 程式碼變得更加優雅和高效的主題:閉包 (Closures) 與迭代器 (Iterators)。
如果說前兩週我們學的是如何「正確地」寫 Rust 程式碼,那麼今天要學的就是如何「優雅地」寫 Rust 程式碼。閉包和迭代器是函數式程式設計的重要概念,它們讓我們能夠以更簡潔、更表達性的方式處理資料和邏輯。
老實說,剛開始接觸函數式程式設計時,我覺得這些語法看起來很「炫」但不太實用。但隨著深入使用,我發現閉包和迭代器不僅讓程式碼更易讀,還能帶來更好的效能!今天讓我們一起探索這個讓 Rust 程式碼變得如詩如畫的美妙世界。
閉包是可以捕獲其環境中變數的匿名函式。你可以把它想像成「會記住周圍環境的函式」:
fn main() {
let x = 4;
// 傳統函式需要明確傳入參數
fn add_x_traditional(num: i32, x: i32) -> i32 {
num + x
}
// 閉包可以直接捕獲環境中的 x
let add_x_closure = |num: i32| num + x;
println!("傳統函式:{}", add_x_traditional(2, x));
println!("閉包:{}", add_x_closure(2));
}
閉包的語法非常靈活,Rust 可以自動推斷大部分型別:
fn main() {
// 完整語法
let add_verbose = |x: i32, y: i32| -> i32 { x + y };
// 省略型別標記(編譯器推斷)
let add_simple = |x, y| x + y;
// 單行表達式
let double = |x| x * 2;
// 多行程式碼塊
let complex_calculation = |x| {
let intermediate = x * 2;
let result = intermediate + 10;
println!("計算中:{} -> {}", x, result);
result
};
println!("詳細加法:{}", add_verbose(3, 4));
println!("簡單加法:{}", add_simple(5, 6));
println!("雙倍:{}", double(7));
println!("複雜計算:{}", complex_calculation(8));
}
閉包可以透過三種方式捕獲環境變數,對應到 Rust 的三種基本所有權操作:
fn main() {
let list = vec![1, 2, 3];
println!("定義閉包前:{:?}", list);
// 閉包只是借用 list
let only_borrows = || println!("從閉包中:{:?}", list);
println!("呼叫閉包前:{:?}", list);
only_borrows();
println!("呼叫閉包後:{:?}", list); // list 仍然可用
}
fn main() {
let mut list = vec![1, 2, 3];
println!("定義閉包前:{:?}", list);
// 閉包可變地借用 list
let mut borrows_mutably = || list.push(7);
// println!("這行會編譯錯誤:{:?}", list); // 不能在可變借用期間使用
borrows_mutably();
println!("呼叫閉包後:{:?}", list);
}
use std::thread;
fn main() {
let list = vec![1, 2, 3];
println!("定義閉包前:{:?}", list);
// 使用 move 關鍵字強制閉包取得所有權
let handle = thread::spawn(move || {
println!("從執行緒中:{:?}", list);
});
// println!("這行會編譯錯誤:{:?}", list); // list 已被移動
handle.join().unwrap();
}
讓我們看一些實際的使用場景:
use std::collections::HashMap;
fn main() {
// 1. 條件過濾
let numbers = vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
let threshold = 5;
let filtered: Vec<i32> = numbers
.into_iter()
.filter(|&x| x > threshold)
.collect();
println!("大於 {} 的數字:{:?}", threshold, filtered);
// 2. 資料轉換
let words = vec!["hello", "world", "rust", "programming"];
let prefix = "Lang: ";
let prefixed: Vec<String> = words
.iter()
.map(|&word| format!("{}{}", prefix, word.to_uppercase()))
.collect();
println!("加上前綴:{:?}", prefixed);
// 3. 計算與聚合
let sales_data = vec![100.0, 250.0, 300.0, 450.0, 200.0];
let tax_rate = 0.1;
let total_with_tax: f64 = sales_data
.iter()
.map(|&amount| amount * (1.0 + tax_rate))
.sum();
println!("含稅總額:{:.2}", total_with_tax);
// 4. 複雜的業務邏輯
let users = vec![
("Alice", 25, "Engineer"),
("Bob", 30, "Designer"),
("Charlie", 35, "Manager"),
("Diana", 28, "Engineer"),
];
let min_age = 27;
let target_role = "Engineer";
let senior_engineers: Vec<&str> = users
.iter()
.filter(|(_, age, role)| *age >= min_age && *role == target_role)
.map(|(name, _, _)| *name)
.collect();
println!("資深工程師:{:?}", senior_engineers);
// 5. 高階函式的使用
let operation = create_multiplier(3);
let results: Vec<i32> = (1..=5)
.map(operation)
.collect();
println!("乘以 3 的結果:{:?}", results);
}
// 回傳閉包的函式
fn create_multiplier(factor: i32) -> impl Fn(i32) -> i32 {
move |x| x * factor
}
迭代器是 Rust 中處理資料集合的主要方式,它們提供了一套豐富且高效的 API:
fn main() {
let vec = vec![1, 2, 3, 4, 5];
// 建立迭代器的三種方式
// 1. iter() - 產生不可變參考的迭代器
for item in vec.iter() {
println!("參考:{}", item); // item 的類型是 &i32
}
// 2. into_iter() - 產生擁有所有權的迭代器
for item in vec.clone().into_iter() {
println!("擁有:{}", item); // item 的類型是 i32
}
// 3. iter_mut() - 產生可變參考的迭代器
let mut vec_mut = vec![1, 2, 3, 4, 5];
for item in vec_mut.iter_mut() {
*item *= 2; // 修改原始值
}
println!("修改後:{:?}", vec_mut);
}
迭代器適配器讓我們能夠轉換迭代器,它們是惰性的(lazy),只有在被消費時才會執行:
fn main() {
let numbers = vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
// 鏈式操作:過濾 -> 映射 -> 收集
let result: Vec<String> = numbers
.iter() // 建立迭代器
.filter(|&&x| x % 2 == 0) // 過濾偶數
.map(|&x| x * x) // 計算平方
.map(|x| format!("{}²", x)) // 轉換為字串
.collect(); // 收集結果
println!("偶數的平方:{:?}", result);
// 更複雜的範例
let words = vec!["hello", "world", "rust", "is", "awesome"];
let processed: Vec<String> = words
.iter()
.enumerate() // 添加索引
.filter(|(i, _)| i % 2 == 0) // 只要偶數索引
.map(|(i, &word)| format!("{}: {}", i, word.to_uppercase()))
.collect();
println!("處理後的單字:{:?}", processed);
}
fn main() {
let numbers = vec![1, 2, 3, 4, 5];
// map - 一對一轉換
let doubled: Vec<i32> = numbers.iter().map(|x| x * 2).collect();
println!("雙倍:{:?}", doubled);
// filter - 條件過濾
let evens: Vec<&i32> = numbers.iter().filter(|&&x| x % 2 == 0).collect();
println!("偶數:{:?}", evens);
// enumerate - 添加索引
let with_index: Vec<(usize, &i32)> = numbers.iter().enumerate().collect();
println!("帶索引:{:?}", with_index);
// zip - 組合兩個迭代器
let letters = vec!['a', 'b', 'c'];
let pairs: Vec<(&i32, &char)> = numbers.iter().zip(letters.iter()).collect();
println!("配對:{:?}", pairs);
// take - 取前 n 個元素
let first_three: Vec<&i32> = numbers.iter().take(3).collect();
println!("前三個:{:?}", first_three);
// skip - 跳過前 n 個元素
let skip_two: Vec<&i32> = numbers.iter().skip(2).collect();
println!("跳過前兩個:{:?}", skip_two);
}
fn main() {
let numbers = vec![1, 2, 3, 4, 5];
// collect - 收集成集合
let collected: Vec<&i32> = numbers.iter().collect();
println!("收集:{:?}", collected);
// fold - 摺疊(類似 reduce)
let sum = numbers.iter().fold(0, |acc, &x| acc + x);
println!("總和:{}", sum);
// reduce - 簡化版的 fold
let product = numbers.iter().reduce(|acc, &x| acc * x);
println!("乘積:{:?}", product);
// for_each - 對每個元素執行動作
print!("迭代:");
numbers.iter().for_each(|x| print!("{} ", x));
println!();
// find - 找到第一個符合條件的元素
let found = numbers.iter().find(|&&x| x > 3);
println!("第一個大於 3 的:{:?}", found);
// any / all - 檢查條件
let has_even = numbers.iter().any(|&&x| x % 2 == 0);
let all_positive = numbers.iter().all(|&&x| x > 0);
println!("有偶數:{},全為正數:{}", has_even, all_positive);
// count - 計算數量
let count = numbers.iter().filter(|&&x| x > 2).count();
println!("大於 2 的數量:{}", count);
}
Rust 的迭代器是「零成本抽象」的典型例子,它們編譯後的效能與手寫的迴圈相當:
fn main() {
let large_vec: Vec<i32> = (0..1_000_000).collect();
// 函數式風格
let sum_functional: i32 = large_vec
.iter()
.filter(|&&x| x % 2 == 0)
.map(|&x| x * x)
.sum();
// 傳統迴圈風格
let mut sum_imperative = 0;
for &item in &large_vec {
if item % 2 == 0 {
sum_imperative += item * item;
}
}
println!("函數式結果:{}", sum_functional);
println!("迴圈結果:{}", sum_imperative);
assert_eq!(sum_functional, sum_imperative);
// 在 release 模式下,這兩種方式的效能幾乎相同!
}
我們也可以為自己的型別實現 Iterator
trait:
struct Counter {
current: usize,
max: usize,
}
impl Counter {
fn new(max: usize) -> Counter {
Counter { current: 0, max }
}
}
impl Iterator for Counter {
type Item = usize;
fn next(&mut self) -> Option<Self::Item> {
if self.current < self.max {
let current = self.current;
self.current += 1;
Some(current)
} else {
None
}
}
}
fn main() {
let counter = Counter::new(5);
// 現在可以使用所有迭代器方法
let doubled: Vec<usize> = counter
.map(|x| x * 2)
.filter(|&x| x > 2)
.collect();
println!("自訂迭代器結果:{:?}", doubled);
// 或者直接在 for 迴圈中使用
for num in Counter::new(3) {
println!("計數:{}", num);
}
}
collect()
fn main() {
let numbers = vec![1, 2, 3, 4, 5];
// ❌ 不必要的中間收集
let intermediate: Vec<i32> = numbers.iter().map(|x| x * 2).collect();
let result: i32 = intermediate.iter().sum();
// ✅ 直接鏈式操作
let result: i32 = numbers.iter().map(|x| x * 2).sum();
println!("結果:{}", result);
}
Iterator::fold
而非多次遍歷fn main() {
let numbers = vec![1, 2, 3, 4, 5];
// ❌ 多次遍歷
let sum: i32 = numbers.iter().sum();
let count = numbers.len();
let max = numbers.iter().max();
// ✅ 單次遍歷
let (sum, count, max) = numbers.iter().fold(
(0, 0, i32::MIN),
|(sum, count, max), &x| (sum + x, count + 1, max.max(x))
);
println!("總和:{},數量:{},最大值:{}", sum, count, max);
}
fn main() {
let numbers = vec![1, 2, 3, 4, 5];
// 查找第一個符合條件的元素
// ✅ 使用 find(短路求值)
let first_even = numbers.iter().find(|&&x| x % 2 == 0);
// ❌ 使用 filter + first(處理全部元素)
let first_even_bad = numbers.iter().filter(|&&x| x % 2 == 0).next();
// 檢查是否存在符合條件的元素
// ✅ 使用 any(短路求值)
let has_even = numbers.iter().any(|&x| x % 2 == 0);
// ❌ 使用 filter + count(處理全部元素)
let has_even_bad = numbers.iter().filter(|&&x| x % 2 == 0).count() > 0;
println!("第一個偶數:{:?},有偶數:{}", first_even, has_even);
}
// JavaScript
const numbers = [1, 2, 3, 4, 5];
const result = numbers
.filter(x => x % 2 === 0)
.map(x => x * x)
.reduce((sum, x) => sum + x, 0);
// Rust
let numbers = vec![1, 2, 3, 4, 5];
let result: i32 = numbers
.iter()
.filter(|&&x| x % 2 == 0)
.map(|&x| x * x)
.sum(); // sum() 是 reduce 的特化版本
# Python
numbers = [1, 2, 3, 4, 5]
result = sum(x * x for x in numbers if x % 2 == 0)
// Rust
let numbers = vec![1, 2, 3, 4, 5];
let result: i32 = numbers
.iter()
.filter(|&&x| x % 2 == 0)
.map(|&x| x * x)
.sum();
// ✅ 好:使用迭代器
fn sum_squares(numbers: &[i32]) -> i32 {
numbers.iter().map(|&x| x * x).sum()
}
// ❌ 較差:手寫迴圈
fn sum_squares_manual(numbers: &[i32]) -> i32 {
let mut sum = 0;
for &number in numbers {
sum += number * number;
}
sum
}
// ✅ 好:函數組合
fn process_users(users: &[User]) -> Vec<String> {
users
.iter()
.filter(|user| user.is_active())
.filter(|user| user.age >= 18)
.map(|user| user.email.to_uppercase())
.collect()
}
// ❌ 較差:巢狀邏輯
fn process_users_nested(users: &[User]) -> Vec<String> {
let mut result = Vec::new();
for user in users {
if user.is_active() {
if user.age >= 18 {
result.push(user.email.to_uppercase());
}
}
}
result
}
fn main() {
let numbers = vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
// 建立可重用的謂詞
let is_even = |x: &i32| *x % 2 == 0;
let is_positive = |x: &i32| *x > 0;
let greater_than = |threshold| move |x: &i32| *x > threshold;
// 組合使用
let result: Vec<&i32> = numbers
.iter()
.filter(is_even)
.filter(is_positive)
.filter(greater_than(5))
.collect();
println!("結果:{:?}", result);
}
今天我們深入探討了 Rust 中函數式程式設計的兩大支柱:
閉包 (Closures):
迭代器 (Iterators):
實用技巧:
collect()
避免不必要的中間集合設計原則:
為什麼重要?
閉包和迭代器是現代 Rust 程式設計的重要工具,它們讓我們能夠寫出既高效又優雅的程式碼。掌握這些概念將大大提升你的 Rust 程式設計水準。
為了鞏固今天的學習,嘗試實作一個日誌分析器:
功能需求:
技術要求:
技術提示:
struct LogEntry {
ip: String,
timestamp: String,
method: String,
url: String,
status: u16,
size: u64,
user_agent: String,
}
struct LogAnalyzer {
entries: Vec<LogEntry>,
}
impl LogAnalyzer {
fn filter_by<F>(&self, predicate: F) -> impl Iterator<Item = &LogEntry>
where
F: Fn(&LogEntry) -> bool,
{
self.entries.iter().filter(predicate)
}
fn status_distribution(&self) -> HashMap<u16, usize> {
// 使用迭代器統計狀態碼分布
}
fn top_pages(&self, limit: usize) -> Vec<(String, usize)> {
// 找出最熱門的頁面
}
fn hourly_traffic(&self) -> Vec<(u8, usize)> {
// 按小時統計流量
}
}
這個挑戰將讓你綜合運用閉包和迭代器來處理實際的資料分析問題。重點是要善用函數式程式設計的思維,寫出既高效又優雅的程式碼。
明天我們將學習 併發 (Concurrency),探討 Rust 如何讓我們安全地編寫多執行緒程式。這是 Rust 的另一個強項,結合今天學到的函數式技巧,將讓我們能夠建立高效能的並行資料處理系統!
如果在實作過程中遇到任何問題,歡迎在留言區討論。函數式程式設計需要一些時間來適應,但一旦掌握,你會發現它讓程式碼變得更加優雅和強大!
我們明天見!