在rust中,匿名函數(或者説閉包)大量存在,所以有必要再次討論匿名函數的一些問題。
其中比較關鍵的是和FnXXX特質的關係,以及和被捕獲變量的關係。
本文的目的在於確認幾個要點:
一、FnOnce,FnMut,Fn簡單比較
比較彙總表
| 分類 | 執行次數 | 是否可以修改捕獲的外部變量 | 是否歸還捕獲的外部變量 | 備註 |
| FnOnce | 一次 | 可以 | 通常歸還,但如果有move,則不會 | 適用於只執行一次的情況 |
| FnMut | 可以多次 | 可以 |
通常歸還,但如果有move,則不會 |
適用於需要修改捕獲外部變量情況 |
| Fn | 可以多次 | 不可以 | 通常歸還,但如果有move,則不會 | 適用於不修改,且多次調用的情況 |
注意:
1.關於所有權是否歸還的問題只是涉及到被捕獲的變量,而非通過參數傳遞的變量。
2.其次,捕獲的變量會不會被歸還,還和是否使用move關鍵字有關
3.如果函數內部修改捕獲的外部變量,則必然不會實現Fn
4.一個匿名函數是可以同時實現多個Fnxxx特質的(而且是自動實現的)
如果一個外部變量在匿名函數中被修改,那麼匿名函數是否使用move都無關緊要,因為就是不寫move,編譯器也會補充上(move)。
換言之,move和FnMut不是必然相關。
move可以用於FnOnce,FnMut,Fn中,要不要用,關鍵看需要,而不是看匿名函數的類型:FnOnce,FnMut,Fn
二、匿名函數變量捕獲要點
a、什麼是捕獲
一個變量不是定義在匿名函數內部,而是在匿名函數主調區域,但是在匿名函數中有使用,那麼就認為該變量被匿名函數捕獲,例如:
let name:String=String::from("21世紀的ai戰爭");
let fx=||{println!("{}",name);};
fx();
在這個例子中,name被fx捕獲了!
b、是否歸還所有權
沒有move就會歸還!
c、肉眼判斷實現了什麼,或者説我怎麼知道一個匿名函數倒是實現了三個特質的哪一個?
那麼如何肉眼判斷一個匿名函數到底實現了三個特質的哪一個? 再不需要寫額外的代碼的情況下.
如前只有如下結論:
1.如果函數內部有修改外部變量,則必然實現了Fn,FnMut,但不會實現Fn
2.關鍵字move不影響實現的具體特質類型
3.如果捕獲變量,但是不修改,無論是否有使用move關鍵字,則都實現了FnOnce,FnMut,Fn
d.使用代碼來判斷一個匿名函數到底實現了什麼特質
#[allow(non_snake_case)]
fn test_FnOnce<T: FnOnce()>(f: T) {
println!("調用FnOnce,只能一次");
f();
}
#[allow(non_snake_case)]
fn test_FnMut<T: FnMut()>(mut f: T) {
println!("調用FnMut,多次執行");
f();
f();
}
#[allow(non_snake_case)]
fn test_Fn<T: Fn()>(f: T) {
println!("調用Fn,多次執行");
f();
f();
}
/**
* 通過調用外部方法驗證某個匿名函數到底實現了什麼Fn特質
*/
fn main() {
let name:String="rust".to_string();
let mut name_mut:String="rust".to_string();
//1.0 一個匿名函數,如果捕獲外部變量,但是並不對變量做修改,則實現了FnOnce, FnMut和Fn特質
let fx1_no_move_no_mut=||println!("{}",name);
test_FnOnce(fx1_no_move_no_mut);
test_FnMut(fx1_no_move_no_mut);
test_Fn(fx1_no_move_no_mut);
println!("{}",name);
// 2~3的測試表明
// a.如果內部修改了變量,但是不使用move,那麼實現了FnOnce,FnMut特質(但不會實現Fn特質).變量歸還
// b.如果內部沒有修改變量,但是使用了move,那麼實現了FnOnce, FnMut和Fn特質,變量不歸還
//2.0 一個匿名函數,捕獲外部變量,但是對變量做了修改,則實現了FnOnce, FnMut特質(但不會實現Fn特質)
let mut fx2_no_move_mut=||{
name_mut.push_str("!fx2_no_move_mut");
println!("{}",name_mut);
};
//test_FnOnce(fx2_no_move_mut);
test_FnMut(fx2_no_move_mut);
println!("歸還後:{}",name_mut);
//3.0 一個匿名函數,如果捕獲外部變量,但是並不對變量做修改,則實現了FnOnce, FnMut和Fn特質
//但因為使用move,所以是不歸還
let fx3_move_no_mut= move ||println!("{}",name);
//test_FnOnce(fx3_move_no_mut);
//test_FnMut(fx3_move_no_mut);
test_Fn(fx3_move_no_mut);
//println!("{}",name);
//4.0 實現FnOnce,FnMut.不歸還
let fx4_move_mut= move ||{
name_mut.push_str("!fx4_move_mut");
println!("{}",name_mut);
};
//test_FnOnce(fx4_move_mut);
//test_FnMut(fx4_move_mut);
//println!("第二次歸還後:{}",name_mut); //已經move了不會歸還
}
在測試代碼中,通過通用參數+特質限定的方式測試一個匿名函數自動實現的Fnxxx特質。
三、如何利用FnXXX特質
主要通過特質綁定的方式進行利用,例如:
let boxFx3: Box<dyn FnOnce()> = Box::new(fx3);
fn test_Fn<T: Fn()>(f: T) {
println!("調用Fn,多次執行");
f();
f();
}
當用上特質綁定的時候,常常需要關聯到dyn(動態分發).
動態分發不是必須,關鍵看實際傳遞什麼。
在rust中,Fnxxx特質大量用於限定函數/方法的參數
四、綜合示例
#[derive(Debug)]
struct Book {
title: String,
author: String,
age: u32,
}
impl Book {
fn new(title: &str, author: &str, age: u32) -> Self {
Book { title: title.to_string(), author: author.to_string(), age: age }
}
fn print(&self) {
println!("{} 作者 {}(發行時年齡{})", self.title, self.author, self.age);
}
}
#[allow(non_snake_case)]
fn test_FnOnce<T: FnOnce()>(f: T) {
println!("調用FnOnce,只能一次");
f();
}
#[allow(non_snake_case)]
fn test_FnMut<T: FnMut()>(mut f: T) {
println!("調用FnMut,多次執行");
f();
f();
}
#[allow(non_snake_case)]
fn test_Fn<T: Fn()>(f: T) {
println!("調用Fn,多次執行");
f();
f();
}
fn main() {
fn_test();
fn_check();
}
/**
* 主要通過特質綁定的方式限定匿名函數的特質,從而限定匿名函數的行為。
*/
fn fn_test() {
//1.0 測試FnOnce特質
let book1 = Book::new("唐詩三百首", "孫洙(蘅塘退士)", 54);
let f1 = || {
book1.print();
};
test_FnOnce(f1);
//這個ts.print還可以繼續使用,説明它被FnOnce歸還了。
book1.print();
//2.0 測試FnMut特質
println!("-----------------------------------------");
let mut book2 = Book::new("Rust程序設計語言", "Steve Klabnik, Carol Nichols", 45);
println!("book2地址: {:p}", &book2);
let mut f2 = move || {
book2.age += 1;
book2.print();
//這裏可以明顯看出變量地址發生了變化,因為所有權轉移了
println!("book2地址: {:p}", &book2);
};
test_FnMut(f2);
//println!("{}",book2.age); //book1不可用是因為move轉移了所有權,且FnMut需要可變借用
println!("-----------------------------------------");
let book3 = Book::new("認識兒童繪畫的特定作用", "盧ml", 13);
println!("book3地址: {:p}", &book3);
let f3 = || {
println!("閉包內book3地址: {:p}", &book3);
book3.print();
};
test_Fn(f3);
println!("{}", book3.age); //book2仍然可用,因為Fn只捕獲了不可變引用
println!("外部book3地址: {:p}", &book3); //驗證地址是否相同
}
/**
* 通過綁定的方式改變匿名函數所實現的特質
* 檢測move關鍵字的作用:用還是不用其實不重要,主要靠編譯器推斷
*/
fn fn_check() {
println!("------------------------------------------------------");
println!("靠肉眼識別實現了哪一種Fn?");
println!("------------------------------------------------------");
let mut name: String = String::from("21世紀的ai戰爭");
//這裏fx無論是否move都無所謂,因為FnMut必然會自動move
println!("只要匿名函數內有修改外部變量,必然實現了FnMut,也必然實現了FnOnce特質。");
let fx = || {
name.push_str(",人類將如何應對?");
println!("{}", name);
};
let mut boxFx: Box<dyn FnMut()> = Box::new(fx);
boxFx();
//println!("{}",name); // 已經move了,所以這裏會報錯
//一個匿名函數是實現了Fn還是FnOnce,純純地看如何定義的。
let name2: String = String::from("認識世界,認識自己從來都不是一件簡單當然事情");
println!("只要匿名函數內沒有修改外部變量,必然實現了Fn特質。");
let fx2 = || {
println!("{}", name2);
};
let boxFx2: Box<dyn Fn()> = Box::new(fx2);
boxFx2();
boxFx2();
println!("雖然fx3和fx2是一摸一樣的,但是被boxFx3約束為FnOnce特質,所以不能再調用第二次。");
let name3: String = String::from("嫁女與征夫,不如棄路旁");
let fx3 = || {
println!("{}", name3);
};
let boxFx3: Box<dyn FnOnce()> = Box::new(fx3);
boxFx3();
//boxFx3(); //再調用一次會報錯,因為強制使用FnOnce約束
}
fn_test中還通過變量的地址來驗證所有權是否改變
println!("book3地址: {:p}", &book3);
這裏使用宏println的:p格式
附:測試輸出

關聯文章