Closures are anonymous functions you can store in a variable or pass as an argument. Unlike regular functions, closures can capture values from the scope around them. Rust infers the types of a closure's parameters and return value in most cases.
Closure là các hàm ẩn danh mà bạn có thể lưu trong biến hoặc truyền dưới dạng đối số. Không giống như hàm thông thường, closure có thể bắt các giá trị từ phạm vi xung quanh chúng. Rust suy ra kiểu của các tham số và giá trị trả về của closure trong hầu hết các trường hợp.
1fn main() {2 // Regular function3 fn add_one_fn(x: i32) -> i32 { x + 1 }45 // Equivalent closure — types inferred6 let add_one = |x| x + 1;78 // Closure with explicit types (verbose, rarely needed)9 let add_one_typed = |x: i32| -> i32 { x + 1 };1011 // Multi-statement closure with braces12 let print_and_return = |x: i32| {Closures can capture variables from their surrounding environment in three ways: immutable borrow (Fn), mutable borrow (FnMut), or taking ownership (FnOnce). The compiler automatically determines the minimum capture mode needed.
Closure có thể bắt giữ biến từ môi trường xung quanh theo ba cách: mượn bất biến (Fn), mượn có thể thay đổi (FnMut), hoặc lấy quyền sở hữu (FnOnce). Trình biên dịch tự động quyết định cách bắt giữ tối thiểu cần thiết.
1fn main() {2 // Fn: borrows immutably — can be called multiple times3 let message = String::from("hello");4 let print_msg = || println!("{message}");5 print_msg(); // hello6 print_msg(); // hello — still works7 println!("{message}"); // message still accessible89 // FnMut: borrows mutably — can be called multiple times10 let mut count = 0;11 let mut increment = || {12 count += 1;Fn là tập con của FnMut, FnMut là tập con của FnOnce. Một closure triển khai Fn cũng là FnMut và FnOnce. Dùng ràng buộc Fn trừ khi bạn cần đột biến hoặc tiêu thụ.
When a function accepts a closure as a parameter you use Fn, FnMut, or FnOnce as a generic trait bound. This is how iterator methods like map and filter accept callbacks.
Khi một hàm nhận closure làm tham số, bạn sử dụng trait Fn, FnMut, hoặc FnOnce làm ràng buộc generic. Đây là cách các phương thức iterator như map và filter nhận callback.
1// Accept a closure that takes i32 and returns bool2fn apply_filter(numbers: &[i32], predicate: impl Fn(i32) -> bool) -> Vec<i32> {3 numbers.iter().filter(|&&x| predicate(x)).copied().collect()4}56// Accept a closure that can mutate state (FnMut)7fn apply_n_times<F: FnMut()>(mut f: F, n: usize) {8 for _ in 0..n { f(); }9}1011// Accept a closure that consumes its captures (FnOnce)12fn run_once<F: FnOnce() -> String>(f: F) -> String {Because closures have anonymous types you cannot return them using a concrete type. Two options: impl Fn in return position (static dispatch) or Box<dyn Fn> (dynamic dispatch, needed when the return type depends on runtime conditions).
Vì closure có kiểu ẩn danh, bạn không thể trả về chúng bằng cách sử dụng kiểu cụ thể. Có hai cách: impl Fn trong vị trí trả về (dispatch tĩnh) hoặc Box<dyn Fn> (dispatch động, cần khi kiểu trả về phụ thuộc vào runtime).
1// Return impl Fn — same concrete closure type always returned2fn make_adder(x: i32) -> impl Fn(i32) -> i32 {3 move |n| n + x4}56// Return impl Fn with captured state7fn make_greeting(prefix: String) -> impl Fn(&str) -> String {8 move |name| format!("{prefix}, {name}!")9}1011// Return Box<dyn Fn> — when the closure type varies at runtime12fn make_operation(double: bool) -> Box<dyn Fn(i32) -> i32> {The Iterator trait is deeply integrated into Rust. Any type implementing Iterator can be used with for loops and the entire ecosystem of adaptors. The core protocol has just one method: next().
Trait Iterator được tích hợp sâu vào Rust. Bất kỳ kiểu nào triển khai Iterator đều có thể dùng với vòng lặp for và toàn bộ hệ sinh thái các bộ điều hợp (adaptor). Giao thức cốt lõi chỉ có một phương thức: next().
1// The Iterator trait definition:2// pub trait Iterator {3// type Item;4// fn next(&mut self) -> Option<Self::Item>;5// // ~70 default methods built on next()6// }78struct Counter {9 count: u32,10 max: u32,11}12Iterator adaptors are methods that take an iterator and return a different iterator. They are lazy — they perform no work until the iterator is consumed. You can chain many adaptors together.
Bộ điều hợp iterator là các phương thức nhận một iterator và trả về một iterator khác. Chúng được lười biếng — chúng không thực hiện bất kỳ công việc nào cho đến khi iterator được tiêu thụ. Bạn có thể xâu chuỗi nhiều bộ điều hợp lại với nhau.
1fn main() {2 let numbers = vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10];34 // map: transform each element5 let doubled: Vec<i32> = numbers.iter().map(|&x| x * 2).collect();6 println!("{doubled:?}"); // [2, 4, 6, 8, 10, 12, 14, 16, 18, 20]78 // filter: keep elements matching predicate9 let evens: Vec<&i32> = numbers.iter().filter(|&&x| x % 2 == 0).collect();10 println!("{evens:?}"); // [2, 4, 6, 8, 10]1112 // chain: concatenate two iteratorsConsuming adaptors are methods that call next() to exhaust the iterator and produce a final value. Every iterator pipeline must end with a consuming adaptor.
Bộ tiêu thụ iterator là các phương thức gọi next() để tiêu thụ iterator và tạo ra kết quả cuối cùng. Mỗi pipeline iterator cần kết thúc bằng một bộ tiêu thụ.
1fn main() {2 let numbers = vec![1, 2, 3, 4, 5];34 // collect: materialize into any collection5 let doubled: Vec<i32> = numbers.iter().map(|&x| x * 2).collect();6 let as_set: std::collections::HashSet<i32> = numbers.iter().copied().collect();78 // sum and product9 let total: i32 = numbers.iter().sum();10 let product: i32 = numbers.iter().product();11 println!("sum={total}, product={product}"); // 15, 12012Implementing the Iterator trait for your struct lets it participate in the entire iterator ecosystem: for loops, adaptors, consuming methods — all for free just by implementing next().
Triển khai trait Iterator cho struct của bạn cho phép nó tham gia vào toàn bộ hệ sinh thái iterator: vòng lặp for, bộ điều hợp, bộ tiêu thụ — tất cả miễn phí chỉ bằng cách triển khai next().
1struct Fibonacci {2 curr: u64,3 next: u64,4}56impl Fibonacci {7 fn new() -> Self {8 Fibonacci { curr: 0, next: 1 }9 }10}1112impl Iterator for Fibonacci {Performance: Iterators vs Loops (Zero-Cost Abstractions)
Hiệu Suất: Iterators vs Vòng Lặp (Zero-Cost Abstractions)
Rust guarantees that iterators perform equivalently to hand-written loops. The compiler completely optimizes away the abstraction overhead — this is what Rust calls a zero-cost abstraction.
Rust đảm bảo rằng các iterator có hiệu suất tương đương với các vòng lặp thủ công viết tay. Trình biên dịch tối ưu hóa hoàn toàn overhead của abstraction — đây là cái mà Rust gọi là 'zero-cost abstraction'.
1fn sum_loop(v: &[f64]) -> f64 {2 let mut total = 0.0;3 for &x in v {4 total += x;5 }6 total7}89fn sum_iter(v: &[f64]) -> f64 {10 v.iter().sum()11}12- Use iterator adaptors for pipeline transformations — more concise and expressive
Dùng iterator adaptors cho các biến đổi đường ống — ngắn gọn và biểu đạt hơn
- Use for loops when you need index access or complex control flow (break with value, continue)
Dùng vòng lặp for khi bạn cần truy cập chỉ số hoặc điều khiển luồng phức tạp (break với giá trị, continue)
- Use while let for manual consumption or complex exit conditions
Dùng while let cho việc tiêu thụ thủ công hoặc các điều kiện thoát phức tạp
- Chain adaptors instead of multiple loops — one pass through the data is faster
Xâu chuỗi các adaptor thay vì lặp nhiều lần — một lượt qua dữ liệu nhanh hơn
- collect() can produce any collection — Vec, HashSet, HashMap, String
collect() có thể tạo ra bất kỳ tập hợp nào — Vec, HashSet, HashMap, String
- Use iter() for immutable refs, iter_mut() for mutable refs, into_iter() to consume
Dùng iter() cho tham chiếu bất biến, iter_mut() cho tham chiếu có thể thay đổi, into_iter() để tiêu thụ
Vẻ đẹp của iterator trong Rust: bạn nhận được code biểu đạt, có thể đọc được và không phải trả bất kỳ chi phí hiệu suất nào so với C viết tay.
Key Takeaways
Điểm Chính
- Closures capture variables from their enclosing scopeClosure bắt giữ biến từ phạm vi bao quanh
- Fn, FnMut, and FnOnce determine how closures capture valuesFn, FnMut và FnOnce xác định cách closure bắt giữ giá trị
- Iterator adapters like map and filter are lazy until consumedAdapter iterator như map và filter lười cho đến khi được tiêu thụ
- Iterator chains compile to the same performance as manual loopsChuỗi iterator biên dịch thành hiệu suất tương đương vòng lặp thủ công
Practice
Test your understanding of this chapter
Which closure trait is required when a closure takes ownership of a captured variable and can only be called once?
Trait closure nào được yêu cầu khi một closure lấy quyền sở hữu biến bắt giữ và chỉ có thể được gọi một lần?
Iterator adaptors like map and filter are described as lazy. What does this mean in practice?
Các bộ điều hợp iterator như map và filter được mô tả là lười biếng. Điều này có nghĩa là gì trong thực tế?
Fn is a subtrait of FnMut and FnMut is a subtrait of FnOnce, so a closure that implements Fn also satisfies FnMut and FnOnce bounds.
Fn là subtrait của FnMut và FnMut là subtrait của FnOnce, vì vậy một closure triển khai Fn cũng thỏa mãn các ràng buộc FnMut và FnOnce.
Rust iterator chains such as .filter().map().collect() are less performant than equivalent hand-written for loops because of abstraction overhead.
Chuỗi iterator Rust như .filter().map().collect() kém hiệu quả hơn các vòng lặp for viết tay tương đương do chi phí abstraction.
Complete the lazy iterator pipeline
Hoàn thành pipeline iterator lười biếng
let result: Vec<i32> = (1..=20) .filter(|x| x % 2 == 0) .map(|x| x * x) .(5) .collect(); // result == [4, 16, 36, 64, 100]