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中閉包的概念並不好理解,因為牽扯到了太多所有權的概念,可以先把所有權弄懂了,閉包也就好理解了。


注意!

本站转载的文章为个人学习借鉴使用,本站对版权不负任何法律责任。如果侵犯了您的隐私权益,请联系我们删除。



 
粤ICP备14056181号  © 2014-2021 ITdaan.com