Rust基礎筆記:閉包


 語法

Closure看上去是這樣的:

    let plus_one = |x: i32| x + 1; assert_eq!(2, plus_one(1));

首先創建一個綁定plus_one,然后將它分配給一個closure,body是一個expression,注意{ } 也是一個expression。

它也可以被寫成這樣:

    let plus_two = |x| { let mut result: i32 = x; result += 1; result += 1; result }; assert_eq!(4, plus_two(2)); 

和常規的函數定義相比,區別就是closure沒有使用關鍵詞 fn ,區分一下:

fn plus_one_v1 (x: i32) -> i32 { x + 1 } let plus_one_v2 = |x: i32| -> i32 { x + 1 }; let plus_one_v3 = |x: i32| x + 1 ; 

值得注意的是在closure中參數和返回值的類型都是可以省略的,下面這種形式也是可以的:

let plus_one = |x| x + 1;

閉包和它的環境

一個小例子:

    let num = 5; let plus_num = |x: i32| x + num; assert_eq!(10, plus_num(5)); 

也就是說,plus_num引用了一個在它作用於中的變量num,具體地說這是一個borrow,它滿足所有權系統的要求,來看一個錯誤的例子:

let mut num = 5; let plus_num = |x: i32| x + num; let y = &mut num; error: cannot borrow `num` as mutable because it is also borrowed as immutable let y = &mut num; ^~~

在上面的代碼中,plus_num已經對num做了不可變引用,而在plus_one的作用域內,又發生了一次可變引用,所以就違反了所有權系統中的如下規則:

如果對一個綁定進行了不可變引用,那么在該引用未超出作用域之前,不可以再進行可變引用,反之也是一樣。

對代碼做出如下修改即可:

    let mut num = 5; { let plus_num = |x: i32| x + num; } // plus_num goes out of scope, borrow of num ends let y = &mut num; 

再看一個例子:

    let nums = vec![1, 2, 3]; let takes_nums = || nums; println!("{:?}", nums); 

有問題嗎?
有,而且是大問題,編譯器的報錯如下:


closure.rs:8:19: 8:23 error: use of moved value: `nums` [E0382] closure.rs:8 println!("{:?}", nums); 

從錯誤中可以看出來,在最后一個輸出語句中,nums已經沒有對資源 vec![1, 2, 3] 的 所有權了,該資源的所有權已經被move到了closure中去了。

那么問題來了:

為什么在前面的例子中closure是borrow,而到了這里就變成了move了呢? 

我們從頭梳理一遍:

    let mut num = 5; let plus_num = || num + 1; let num2 = &mut num; 
Error: closure.rs:5:21: 5:24 error: cannot borrow `num` as mutable because it is also borrowed as immutable closure.rs:5 let num2 = &mut num; 

說明在closure中發生了immutable borrow,這樣才會和下面的&mut沖突,現在我們來做一個改動:

    let plus_num = || num + 1; // 改成如下語句 let mut plue_num = || num += 1;

再編譯一次:

Error: closure.rs:4:17: 4:20 error: cannot borrow `num` as mutable more than once at a time closure.rs:4 let num2 = &mut num; 

可以發現,在closure中發生了mutable borrow,為什么會這樣呢?

在closure無非就是這3種情況:

  • by reference: &T

  • by mutable reference: &mut T

  • by value: T

    至於是這3個中的哪一個,取決於你closure內部怎么用,然后編譯器自動推斷綁定的類型是Fn() FnMut() 還是FnOnce()

    let plus_num = || num + 1; // 這個只需要引用即可,所以plus_num類型為Fn() let mut plue_num = || num += 1; // 這個則需要&mut T,所以plus_num類型為FnMut() // 這是手冊里的一個例子 // 這是一個沒有實現Copy trait的類型 let movable = Box::new(3); // `drop` 需要類型T,所以closure環境就需要 by value T.,所以consume類型為FnOnce() let consume = || { drop(movable); // 這里發生了move }; // 所以這個consume只能執行一次 consume(); 

有一點要注意的是:
在前面的例子應該分成兩類:

  1. let a= 100i32;

  2. let a = vec![1,2,3];

區別就是i32類型實現了copy trait,而vector沒有!!!

參考:http://rustbyexample.com/fn/closures/capture.html

Move closure

使用move關鍵字,強制closure獲得所有權,但下面的例子得注意一下:

    let num = 5; let owns_num = move |x: i32| x + num;

盡管這里使用move,變量遵循move語義,但是,在這里5實現了Copy,所以owns_own獲得的是 5 的拷貝的所有權,有什么區別呢?
來看看這段代碼:

    let mut num = 5; { let mut add_num = |x: i32| num += x; add_num(5); } assert_eq!(10, num); 

這段代碼得到的是我們想要的結果,但是如果我們加上move關鍵字呢?上面的代碼就會報錯,因為num的值仍是 5 ,並沒有發生改變,

為什么呢?
上面說到了,move強制閉包環境獲得所有權,但是 5 實現了Copy,所以閉包獲得的是其拷貝的所有權,同理閉包中修改的也是 5 的拷貝。 

總結

在Rust中閉包的概念並不好理解,因為牽扯到了太多所有權的概念,可以先把所有權弄懂了,閉包也就好理解了。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM