閉包
無論是閉包還是 lambda 運算式,都含有無法命名的型別。不過,這兩者 都會實作特殊的 Fn
、 FnMut
和 FnOnce
特徵:
fn apply_with_log(func: impl FnOnce(i32) -> i32, input: i32) -> i32 { println!("Calling function on {input}"); func(input) } fn main() { let add_3 = |x| x + 3; println!("add_3: {}", apply_with_log(add_3, 10)); println!("add_3: {}", apply_with_log(add_3, 20)); let mut v = Vec::new(); let mut accumulate = |x: i32| { v.push(x); v.iter().sum::<i32>() }; println!("accumulate: {}", apply_with_log(&mut accumulate, 4)); println!("accumulate: {}", apply_with_log(&mut accumulate, 5)); let multiply_sum = |x| x * v.into_iter().sum::<i32>(); println!("multiply_sum: {}", apply_with_log(multiply_sum, 3)); }
This slide should take about 20 minutes.
Fn
(例如 add_3
) 既不會耗用也不會修改擷取的值,或許 也可說是不會擷取任何值,因此可以多次並行呼叫。
FnMut
(例如 accumulate
) 可能會修改擷取的值,因此可以多次呼叫 (但不得並行呼叫)。
如果是 FnOnce
(例如multiply_sum
),也許就只能呼叫一次,因為這可能會耗用 擷取的值。
FnMut
是 FnOnce
的子型別,而 Fn
是 FnMut
和 FnOnce
的子型別。換句話說,您可以在任何需要呼叫 FnOnce
的地方使用 FnMut
,而在任何需要呼叫 FnMut
或 FnOnce
的地方 使用 Fn
。
定義可接受閉包的函式時,您應盡量採用 FnOnce
(也就是只呼叫一次),其次是 FnMut
,最後則是 Fn
。這種做法可讓呼叫端享有最大彈性。
相反地,當有閉包時,最有彈性的就是 Fn
(可以在任何地方傳遞)、其次是 FnMut
,最後是 FnOnce
。
編譯器也會根據閉包擷取到的內容來推論 Copy
(例如針對 add_3
) 和 Clone
(例如 multiply_sum
).
根據預設,閉包會依據參照來擷取內容 (如果可行的話)。move
關鍵字則可讓閉包根據值 來擷取內容。
fn make_greeter(prefix: String) -> impl Fn(&str) { return move |name| println!("{} {}", prefix, name); } fn main() { let hi = make_greeter("Hi".to_string()); hi("Greg"); }