語法
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();
有一點要注意的是:
在前面的例子應該分成兩類:
-
let a= 100i32;
-
let a = vec![1,2,3];
區別就是i32類型實現了copy trait,而vector沒有!!!
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中閉包的概念並不好理解,因為牽扯到了太多所有權的概念,可以先把所有權弄懂了,閉包也就好理解了。
