閉包看起來(lái)像這樣:
let plus_one = |x: i32| x + 1;
assert_eq!(2, plus_one(1));
我們創(chuàng)建了一個(gè)綁定,plus_one
,并把它賦予一個(gè)閉包。閉包的參數(shù)位于管道(|
)之中,而閉包體是一個(gè)表達(dá)式,在這個(gè)例子中,x + 1
。記住{}
是一個(gè)表達(dá)式,所以我們也可以擁有包含多行的閉包:
let plus_two = |x| {
let mut result: i32 = x;
result += 1;
result += 1;
result
};
assert_eq!(4, plus_two(2));
你會(huì)注意到閉包的一些方面與用fn
定義的常規(guī)函數(shù)有點(diǎn)不同。第一個(gè)是我們并不需要標(biāo)明閉包接收和返回參數(shù)的類型。我們可以:
let plus_one = |x: i32| -> i32 { x + 1 };
assert_eq!(2, plus_one(1));
不過(guò)我們并不需要這么寫。為什么呢?基本上,這是出于“人體工程學(xué)”的原因。因?yàn)闉槊瘮?shù)指定全部類型有助于像文檔和類型推斷,而閉包的類型則很少有文檔因?yàn)樗鼈兪悄涿模⑶也⒉粫?huì)產(chǎn)生像推斷一個(gè)命名函數(shù)的類型這樣的“遠(yuǎn)距離錯(cuò)誤”。
第二個(gè)的語(yǔ)法大同小異。我會(huì)增加空格來(lái)使它們看起來(lái)更像一點(diǎn):
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 ;
之所以把它稱為“閉包”是因?yàn)樗鼈儭鞍诃h(huán)境中”(close over their environment)。這看起來(lái)像:
let num = 5;
let plus_num = |x: i32| x + num;
assert_eq!(10, plus_num(5));
這個(gè)閉包,plus_num
,引用了它作用域中的let
綁定:num
。更明確的說(shuō),它借用了綁定。如果我們做一些會(huì)與這個(gè)綁定沖突的事,我們會(huì)得到一個(gè)錯(cuò)誤。比如這個(gè):
let mut num = 5;
let plus_num = |x: i32| x + num;
let y = &mut num;
錯(cuò)誤是:
error: cannot borrow `num` as mutable because it is also borrowed as immutable
let y = &mut num;
^~~
note: previous borrow of `num` occurs here due to use in closure; the immutable
borrow prevents subsequent moves or mutable borrows of `num` until the borrow
ends
let plus_num = |x| x + num;
^~~~~~~~~~~
note: previous borrow ends here
fn main() {
let mut num = 5;
let plus_num = |x| x + num;
let y = &mut num;
}
^
一個(gè)啰嗦但有用的錯(cuò)誤信息!如它所說(shuō),我們不能取得一個(gè)num
的可變借用因?yàn)殚]包已經(jīng)借用了它。如果我們讓閉包離開(kāi)作用域,我們可以:
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;
如果你的閉包需要它,Rust會(huì)取得所有權(quán)并移動(dòng)環(huán)境:
let nums = vec![1, 2, 3];
let takes_nums = || nums;
println!("{:?}", nums);
這會(huì)給我們:
note: `nums` moved into closure environment here because it has type
`[closure(()) -> collections::vec::Vec<i32>]`, which is non-copyable
let takes_nums = || nums;
^~~~~~~
Vec<T>
擁有它內(nèi)容的所有權(quán),而且由于這個(gè)原因,當(dāng)我們?cè)陂]包中引用它時(shí),我們必須取得nums
的所有權(quán)。這與我們傳遞nums
給一個(gè)取得它所有權(quán)的函數(shù)一樣。
我們可以使用move
關(guān)鍵字強(qiáng)制使我們的閉包取得它環(huán)境的所有權(quán):
let num = 5;
let owns_num = move |x: i32| x + num;
現(xiàn)在,即便關(guān)鍵字是move
,變量遵循正常的移動(dòng)語(yǔ)義。在這個(gè)例子中,5
實(shí)現(xiàn)了Copy
,所以owns_num
取得一個(gè)5
的拷貝的所有權(quán)。那么區(qū)別是什么呢?
let mut num = 5;
{
let mut add_num = |x: i32| num += x;
add_num(5);
}
assert_eq!(10, num);
那么在這個(gè)例子中,我們的閉包取得了一個(gè)num
的可變引用,然后接著我們調(diào)用了add_num
,它改變了其中的值,正如我們期望的。我們也需要將add_num
聲明為mut
,因?yàn)槲覀儠?huì)改變它的環(huán)境。
如果我們加上move
修飾閉包,會(huì)發(fā)生些不同:
let mut num = 5;
{
let mut add_num = move |x: i32| num += x;
add_num(5);
}
assert_eq!(5, num);
我們只會(huì)得到5
。這次我們沒(méi)有獲取到外部的num
的可變借用,我們實(shí)際上是把 num
move 進(jìn)了閉包。因?yàn)?num
具有 Copy 屬性,因此發(fā)生 move 之后,以前的變量生命周期并未結(jié)束,還可以繼續(xù)在 assert_eq!
中使用。我們打印的變量和閉包內(nèi)的變量是獨(dú)立的兩個(gè)變量。如果我們捕獲的環(huán)境變量不是 Copy 的,那么外部環(huán)境變量被 move 進(jìn)閉包后,
它就不能繼續(xù)在原先的函數(shù)中使用了,只能在閉包內(nèi)使用。
不過(guò)在我們討論獲取或返回閉包之前,我們應(yīng)該更多的了解一下閉包實(shí)現(xiàn)的方法。作為一個(gè)系統(tǒng)語(yǔ)言,Rust給予你了大量的控制你代碼的能力,而閉包也是一樣。
這部分引用自The Rust Programming Language中文版