Stories

Detail Return Return

rust進階-基礎.1.匿名函數和FnXXX特質 - Stories Detail

在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格式

 

附:測試輸出

 

 

關聯文章

rust學習十三.1、RUST匿名函數(閉包)

rust學習二十.13、RUST的函數指針fn和匿名函數

 
 

Add a new Comments

Some HTML is okay.