Pass variables to closure
Closures trong Rust có thể capture variables từ môi trường xung quanh. Tuy nhiên, việc pass variables vào closure đôi khi gặp các vấn đề với ownership và borrowing. Có nhiều patterns để giải quyết vấn đề này.
Vấn đề: Closure captures by reference
Mặc định, closures capture variables bằng reference:
fn main() { let s = String::from("hello"); let closure = || { println!("{}", s); // Capture by reference }; closure(); println!("{}", s); // s vẫn valid }
Nhưng điều này gây vấn đề khi closure cần outlive scope:
#![allow(unused)] fn main() { fn create_closure() -> impl Fn() { let s = String::from("hello"); // ❌ Compile error! // || println!("{}", s) // s doesn't live long enough } }
Solution 1: Move closure
Sử dụng move keyword để transfer ownership:
fn create_closure() -> impl Fn() { let s = String::from("hello"); // ✅ Move ownership vào closure move || println!("{}", s) } fn main() { let closure = create_closure(); closure(); // "hello" }
Solution 2: Clone before move
Khi cần giữ lại original value:
fn main() { let s = String::from("hello"); // Clone trước khi move let s_clone = s.clone(); let closure = move || { println!("{}", s_clone); }; closure(); println!("Original: {}", s); // s vẫn còn }
Solution 3: Explicit move trong closure
fn main() { let s = String::from("hello"); let closure = { let s = s.clone(); // Clone trong block move || println!("{}", s) }; println!("Original: {}", s); // s vẫn còn closure(); }
Pattern: Rebinding với move
Một pattern phổ biến là rebinding variable với cùng tên:
fn main() { let data = vec![1, 2, 3]; // Rebind để cho rõ ràng let data = data.clone(); let closure = move || { println!("{:?}", data); }; // data original đã bị moved closure(); }
Working với multiple variables
❌ Vấn đề: Partial move
fn main() { let x = String::from("x"); let y = String::from("y"); let closure = move || { println!("{}", x); // Only need x }; // ❌ Compile error! y cũng bị moved // println!("{}", y); }
✅ Solution: Selective cloning
fn main() { let x = String::from("x"); let y = String::from("y"); let x = x; // Chỉ move x let closure = move || { println!("{}", x); }; println!("{}", y); // y vẫn available closure(); }
Use case: Thread spawning
use std::thread; fn main() { let data = vec![1, 2, 3, 4, 5]; // Clone để thread có ownership let data_clone = data.clone(); let handle = thread::spawn(move || { let sum: i32 = data_clone.iter().sum(); println!("Sum: {}", sum); }); // data vẫn available trong main thread println!("Original: {:?}", data); handle.join().unwrap(); }
Pattern: Reference counted (Arc)
Khi cần share data giữa nhiều closures/threads:
use std::sync::Arc; use std::thread; fn main() { let data = Arc::new(vec![1, 2, 3, 4, 5]); let mut handles = vec![]; for i in 0..3 { let data = Arc::clone(&data); let handle = thread::spawn(move || { println!("Thread {}: {:?}", i, data); }); handles.push(handle); } for handle in handles { handle.join().unwrap(); } }
Capturing specific fields
Khi chỉ cần capture một field từ struct:
struct Config { name: String, value: i32, } fn main() { let config = Config { name: "test".to_string(), value: 42, }; // Chỉ capture field cần thiết let name = config.name.clone(); let closure = move || { println!("Name: {}", name); }; // config.value vẫn available println!("Value: {}", config.value); closure(); }
Pattern: Combinator style
fn main() { let numbers = vec![1, 2, 3, 4, 5]; let threshold = 3; // Clone threshold để use nhiều lần let filtered: Vec<i32> = numbers .iter() .filter(|&&n| n > threshold) .copied() .collect(); println!("{:?}", filtered); }
Closure với mutable variables
fn main() { let mut count = 0; { let mut closure = || { count += 1; println!("Count: {}", count); }; closure(); // 1 closure(); // 2 } println!("Final: {}", count); // 2 }
Move với mutable
fn main() { let mut count = 0; let mut closure = move || { count += 1; // Modifying moved copy count }; println!("{}", closure()); // 1 println!("{}", closure()); // 2 // Original count vẫn là 0 println!("Original: {}", count); // 0 }
Real-world example: Event handlers
struct Button { on_click: Box<dyn Fn()>, } impl Button { fn new<F>(on_click: F) -> Self where F: Fn() + 'static, { Button { on_click: Box::new(on_click), } } fn click(&self) { (self.on_click)(); } } fn main() { let message = String::from("Button clicked!"); let button = Button::new(move || { println!("{}", message); }); button.click(); button.click(); }
Pattern: Builder with closures
struct Request { url: String, on_success: Box<dyn Fn(String)>, on_error: Box<dyn Fn(String)>, } impl Request { fn new(url: String) -> RequestBuilder { RequestBuilder { url, on_success: None, on_error: None, } } } struct RequestBuilder { url: String, on_success: Option<Box<dyn Fn(String)>>, on_error: Option<Box<dyn Fn(String)>>, } impl RequestBuilder { fn on_success<F>(mut self, callback: F) -> Self where F: Fn(String) + 'static, { self.on_success = Some(Box::new(callback)); self } fn on_error<F>(mut self, callback: F) -> Self where F: Fn(String) + 'static, { self.on_error = Some(Box::new(callback)); self } fn send(self) { // Simulate request let success = true; if success { if let Some(callback) = self.on_success { callback("Success!".to_string()); } } else { if let Some(callback) = self.on_error { callback("Error!".to_string()); } } } } fn main() { let prefix = String::from("[LOG]"); Request::new("https://api.example.com".to_string()) .on_success(move |msg| { println!("{} {}", prefix, msg); }) .send(); }
Closure types và capture modes
Rust có 3 closure traits tùy thuộc vào cách capture:
fn main() { let s = String::from("hello"); // FnOnce: Consumes captured variables let consume = move || { drop(s); // Takes ownership }; consume(); // consume(); // ❌ Can't call twice let s = String::from("hello"); // FnMut: Mutably borrows captured variables let mut mutate = || { s.push_str(" world"); // ❌ Won't compile: can't mutate }; let mut s = String::from("hello"); // Fn: Immutably borrows captured variables let borrow = || { println!("{}", s); // Just reads }; borrow(); borrow(); // ✅ Can call multiple times }
Best Practices
- Clone judiciously: Chỉ clone khi cần thiết
- Use
Arccho shared ownership: Đặc biệt với threads - Prefer
movecho long-lived closures: Tránh lifetime issues - Document capture behavior: Ghi rõ closure captures gì
- Consider using structs: Cho complex state management
Common mistakes
❌ Forgetting move with threads
#![allow(unused)] fn main() { // ❌ Won't compile fn wrong() { let data = vec![1, 2, 3]; thread::spawn(|| { println!("{:?}", data); // Error: may outlive borrowed value }); } // ✅ Correct fn correct() { let data = vec![1, 2, 3]; thread::spawn(move || { println!("{:?}", data); }); } }
❌ Moving when you need to borrow
#![allow(unused)] fn main() { fn wrong() { let data = vec![1, 2, 3]; let closure = move || { println!("{:?}", data); }; closure(); // println!("{:?}", data); // ❌ Error: value moved } // ✅ Just borrow fn correct() { let data = vec![1, 2, 3]; let closure = || { println!("{:?}", data); }; closure(); println!("{:?}", data); // ✅ Still available } }