在 Rust,迭代器是關於資料流所有權的語法。
想對了資料,程式碼自然就對了。
你對集合做的第一件事,就決定了整條鏈的效率。選錯了入口,後面就等著用 clone()
來彌補。
iter()
→ 借用元素 (&T
)
用途:讀取、計算、過濾資料。
這是預設選項,90% 的情況下你都該用它。
iter_mut()
→ 可變借用元素 (&mut T
)
用途:原地修改集合中的資料。
當你需要改變現有資料,而不是創造新資料時使用。
into_iter()
→ 移動元素 (T
)
用途:轉移所有權,消耗原集合,建立一個新集合。
當資料的生命週期需要延續到集合之外時使用。
入口選對,整條鏈就不該有 .clone()
。
像 map()
, filter()
, take()
這樣的鏈式方法,它們本身幾乎不做任何事。
它們只是在建立一個處理資料的配方(或者叫「視圖」),這個過程沒有記憶體分配,成本極低。
為什麼鏈式操作中間不能有 .clone()
?
.clone()
就是你邏輯混亂的證明。正確的做法是回到起點,直接選 3(我要拿走)。真正的計算和記憶體分配,發生在鏈的末端,被稱之為「收斂點」或「消費者」。
collect()
:根據你指定的型別,把配方計算出來,變成一個新的集合。這是付費的地方。
sum()
, count()
, for_each()
:消耗迭代器,計算出最終結果。
心智模型:建立一個廉價的配方,在最後一刻才執行它並支付成本。
// 1. iter(): 讀取資料來計算
let data = vec!["a".to_string(), "b".to_string()];
// data 在此之後依然可用
let lens: Vec<usize> = data.iter() // &String
.map(|s| s.len())
.collect();
// 2. iter_mut(): 原地修改
let mut nums = vec![1, 2, 3];
// nums 在此之後被修改了
nums.iter_mut() // &mut i32
.for_each(|n| *n += 1);
// 3. into_iter(): 轉移所有權
// data 在此之後被消耗,無法再使用
let moved: Vec<String> = data.into_iter() // String
.collect();
只有三個選擇:
我就看看,不動它。
iter()
。它會給你一個指向資料的「唯讀通行證」(&T
)。你只能看,不能改,更不能拿走。原來的東西還在。我要修改它,讓它在原地變個樣子。
iter_mut()
。它會給你一個「可修改通行證」(&mut T
)。你可以直接在上面塗改。原來的東西被你改了。這東西現在歸我了,我要拿走。
into_iter()
。這是「所有權轉移證明」(T
)。東西被你拿走了,原來的箱子空了,別人不能再用了。.clone()
?.clone()
就是你邏輯混亂的證明。正確的做法是回到起點,直接選 3(我要拿走)。map
, filter
這些東西,只是你紙上寫下的「處理步驟」。電腦很懶,它看到你的計畫,但它不動手。直到最後說 collect()
(交作業),它才一口氣把所有步驟做完。這樣最有效率有時你需要一個擁有其資料的新集合,但你的入口是 iter()
,它只提供參考。
let words = vec!["hello".to_string(), "world".to_string()];
// 我們想要一個 BTreeSet<String>,它需要擁有 String,而不是 &String
let set: std::collections::BTreeSet<String> = words.iter() // 產生 &String
.cloned() // 把 &String 變成 String,這是必要的複製
.collect();
這裡的 .cloned()
很重要。它不是一個錯誤,而是一個明確的所有權轉換。你告訴編譯器:「我知道 iter
只給了我參考,但我現在需要一份擁有權的拷貝來建立新集合。」這是唯一應該在鏈中出現 clone
的合理場景。
永遠先用 iter()
。
需要原地修改?換成 iter_mut()
。
需要轉移所有權或消耗原集合?換成 into_iter()
。
讓 collect()
在最後處理所有分配。
反模式(如果你這麼做,就表示設計錯了):
在 map()
中途 .clone()
或 .to_owned()
:你的入口選錯了。你應該一開始就用 into_iter()
來取得所有權。
用了 into_iter()
還想保留原集合:邏輯衝突。你不能同時吃掉蛋糕又擁有它。回去重新思考你的資料流。
先決定意圖:看、改,還是拿走?
再選擇工具:iter
, iter_mut
, into_iter
。
然後寫下計畫:.map().filter()...
最後執行:.collect()