Rust語(yǔ)言的閉包是一種可以捕獲外部變量并在需要時(shí)執(zhí)行的匿名函數(shù)。閉包在Rust中是一等公民,它們可以像其他變量一樣傳遞、存儲(chǔ)和使用。閉包可以捕獲其定義范圍內(nèi)的變量,并在必要時(shí)訪問(wèn)它們。這使得閉包在許多場(chǎng)景下非常有用,例如迭代器、異步編程和并發(fā)編程。
閉包與函數(shù)的區(qū)別在于,閉包可以捕獲它所定義的環(huán)境中的變量。這意味著,當(dāng)閉包中使用變量時(shí),它可以訪問(wèn)該變量的值。在Rust中,閉包被設(shè)計(jì)為可以自動(dòng)推斷變量的類型,因此可以方便地使用。
Rust閉包概念和python中Lambda表達(dá)式,Java的Lambda表達(dá)式很類似,可以幫助理解和應(yīng)用。
閉包的應(yīng)用場(chǎng)景
閉包在Rust語(yǔ)言中被廣泛應(yīng)于許多場(chǎng)景。例如,在多線程編程中,閉包可以用來(lái)定義線程任務(wù)。在Web開(kāi)發(fā)中,閉包可以用來(lái)定義路由處理函數(shù)。在數(shù)據(jù)處理領(lǐng)域,閉包可以用來(lái)定義數(shù)據(jù)轉(zhuǎn)換和過(guò)濾函數(shù)等等。 下面,我們以Animal
為例,演示如何使用閉包實(shí)現(xiàn)一些常見(jiàn)的數(shù)據(jù)處理和轉(zhuǎn)換操作。
use std::collections::HashMap;
#[derive(Debug)]
struct Animal {
name: String,
species: String,
age: i32,
}
impl Animal {
fn new(name: &str, species: &str, age: i32) - > Self {
Animal {
name: name.to_owned(),
species: species.to_owned(),
age,
}
}
}
impl Display for Animal {
fn fmt(&self, f: &mut Formatter) - > Result {
write!(f, "Animal info name {}, species:{}, age:{}", self.name, self.species, self.age)
}
}
fn main() {
let animals = vec![
Animal::new("Tom", "Cat", 2),
Animal::new("Jerry", "Mouse", 1),
Animal::new("Spike", "Dog", 3),
];
// 計(jì)算所有動(dòng)物的平均年齡
let total_age = animals.iter().map(|a| a.age).sum::< i32 >();
let average_age = total_age as f32 / animals.len() as f32;
println!("Average age: {:.2}", average_age);
// 統(tǒng)計(jì)每個(gè)物種的數(shù)量
let mut species_count = HashMap::new();
for animal in &animals {
let count = species_count.entry(animal.species.clone()).or_insert(0);
*count += 1;
}
println!("Species count: {:?}", species_count);
// 找出所有年齡大于2歲的動(dòng)物
let old_animals: Vec< _ > = animals.iter().filter(|a| a.age > 2).collect();
println!("Old animals: {:?}", old_animals);
// 將所有動(dòng)物的名字轉(zhuǎn)換成大寫
let upper_names: Vec< _ > = animals.iter().map(|a| a.name.to_uppercase()).collect();
println!("Upper case names {:?}", upper_names);
}
// 輸出結(jié)果:
// Average age: 2.00
// Species count: {"Dog": 1, "Cat": 1, "Mouse": 1}
// Old animals: [Animal { name: "Spike", species: "Dog", age: 3 }]
// Upper case names ["TOM", "JERRY", "SPIKE"]
在上面的代碼中,我們定義了一個(gè)Animal
結(jié)構(gòu)體,其中包含了動(dòng)物的名稱、物種和年齡信息。我們使用Vec
類型來(lái)存儲(chǔ)所有動(dòng)物的信息。接下來(lái),我們使用包對(duì)這些動(dòng)物進(jìn)行了一些常見(jiàn)的數(shù)據(jù)處理和轉(zhuǎn)換操作。
首先,我們計(jì)算了所有動(dòng)物的平均年齡。我們使用iter()
方法對(duì)Vec
進(jìn)行迭代,并使用map()
方法將每個(gè)動(dòng)物的年齡提取出來(lái)。然后,我們使用sum()
方法將所有的年齡相加,并將其轉(zhuǎn)換為i32
類型。最后,我們將總年齡除以動(dòng)物數(shù)量,得到平均年齡。
接下來(lái),我們統(tǒng)計(jì)了每個(gè)物種的數(shù)量。我們使用HashMap
類型來(lái)存儲(chǔ)物種和數(shù)量的映射關(guān)系。我們使用entry
方法獲取每個(gè)物種的數(shù)量,如果該物種不存在,則插入一個(gè)新的映射關(guān)系,并將數(shù)量初始化為0。最后,我們使用filter()
方法和閉包找出了所有年齡大于2歲的動(dòng)物。我們使用map()
方法和閉包將所有動(dòng)物的名字轉(zhuǎn)換成大寫,然后使用collect()
方法將它們收集到一個(gè)新的Vec
中。最后,我們使用map()
方法和閉包將所有動(dòng)物的名字轉(zhuǎn)換成大寫。
在上面的示例中,我們可以看到閉包的強(qiáng)大之處。使用閉包,我們可以輕松地對(duì)數(shù)據(jù)進(jìn)行轉(zhuǎn)換和處理,而不必定義大量的函數(shù)。此外,閉包還可以捕獲外部環(huán)境中的變量,使得代碼更加靈活和可讀。
閉包的語(yǔ)法
包的語(yǔ)法形式如下:
|arg1, arg2, ...| body
其中,arg1
、arg2
...表示閉包參數(shù),body
表示閉包函數(shù)體。閉包可以有多個(gè)參數(shù),也可以沒(méi)有參數(shù)。如果閉包沒(méi)有參數(shù),則可以省略|
和|
之間的內(nèi)容。
無(wú)參數(shù)閉包示例:
fn main() {
let greet = || println!("Hello, World!");
greet();
}
// 輸出結(jié)果:
// Hello, World!
閉包的函數(shù)體可以是任意有效的Rust代碼,包括表達(dá)式、語(yǔ)句和控制流結(jié)構(gòu)等。在閉包中,我們可以使用外部作用域中的變量。這些變量被稱為閉包的自由變量,因?yàn)樗鼈儾皇情]包參數(shù),但是在閉包中被引用了。
閉包的自由變量示例如下:
fn main() {
let x = 3;
let y = 5;
// 在這里y,就是閉包的自由變量
let add = |a, b| a + b + y;
println!("add_once_fn: {}", add(x,y));
}
// 輸出結(jié)果:
// 13
在上面的示例中,我們定義了一個(gè)閉包add
,沒(méi)用指定參數(shù)的具體類型,這里是使用到了Rust語(yǔ)言的閉包類型推導(dǎo)特性,編譯器會(huì)在調(diào)用的地方進(jìn)行類型推導(dǎo)。這里值得注意的幾點(diǎn)小技巧定義的閉包必須要有使用,否則編譯器缺少類型推導(dǎo)的上下文。當(dāng)編譯器推導(dǎo)出一種類型后,它就會(huì)一直使用該類型,和泛型有本質(zhì)的區(qū)別。
// 1. 將上面例子的pringln!注釋掉, 相當(dāng)于add閉包沒(méi)用任何引用,編譯報(bào)錯(cuò)
error[E0282]: type annotations needed
-- > src/main.rs:13:16
|
13 | let add = |a, b| a + b + y;
| ^
|
help: consider giving this closure parameter an explicit type
|
13 | let add = |a: /* Type */, b| a + b + y;
| ++++++++++++
// 2. 新增打印 println!("add_once_fn: {}", add(0.5,0.6));
error[E0308]: arguments to this function are incorrect
-- > src/main.rs:16:33
|
16 | println!("add_once_fn: {}", add(0.5,0.6));
| ^^^ --- --- expected integer, found floating-point number
| |
| expected integer, found floating-point number
|
note: closure defined here
-- > src/main.rs:13:15
|
13 | let add = |a, b| a + b + y;
| ^^^^^^
閉包可以使用三種方式之一來(lái)捕獲自由變量:
- ?
move
關(guān)鍵字:將自由變量移動(dòng)到閉包內(nèi)部,使得閉包擁有自由變量的所有權(quán)。這意味著,一旦自由變量被移動(dòng),部作用域?qū)o(wú)法再次使用它。 - ?
&
引用:使用引用來(lái)訪問(wèn)自由變量。這意味著,外部作用域仍然擁有自由變量的所有權(quán),并且可以在閉包之后繼續(xù)使用它。 - ?
&mut
可變引用:使用可變引用來(lái)訪問(wèn)自由變量。這意味著,外部作用域仍然擁有自由變量的所有權(quán),并且可以在閉包之后繼續(xù)使用它。但是,只有一個(gè)可變引用可以存在于任意給定的時(shí)間。如果閉包中有多個(gè)可變引用,編譯器將無(wú)法通過(guò)。
下面是具有不同捕獲方式的閉包示例:
fn main() {
let x = 10;
let y = 20;
// 使用move關(guān)鍵字捕獲自由變量
let add = move |a:i32, b:i32| a + b + x;
// 使用引用捕獲自由變量
let sub = |a:i32, b:i32| a - b - y;
// 使用可變引用捕獲自由變量
let mut z = 30;
let mut mul = |a:i32, b:i32| {
z += 1;
a * b * z
};
println!("add {}", add(x, y))
println!("sub {}", sub(x, y))
println!("mul {}", mul(x, y))
}
// 輸出結(jié)果:
// add 40
// sub -30
// mul 6200
在上面的示例中,我們定義了三個(gè)閉包:add
、sub
和mul
。
- ?
add
使用move
關(guān)鍵字捕獲了自由變量x
,因此它擁有x
的所有權(quán)。 - ?
sub
使用引用捕獲了自由變量y
,因此它只能訪問(wèn)y
的值,而不能修改它。 - ?
mul
使用可變引用捕獲了自由變量z
,因此它可以修改z
的值。在這種情況下,我們需要使用mut
關(guān)鍵字來(lái)聲明可變引用。
閉包的類型
在Rust語(yǔ)言中,閉包是一種特殊的類型,被稱為Fn
、FnMut
和FnOnce
。這些類型用于區(qū)分閉包的捕獲方式和參數(shù)類型。
- ?
Fn
:表示閉包只是借用了自由變量,不會(huì)修改它們的值。這意味著,閉包可以在不擁有自由變量所有權(quán)的情況下訪問(wèn)它們。 - ?
FnMut
:表示閉包擁有自由變量的可變引用,并且可能會(huì)修改它們的值。這意味著,閉包必須擁有自由變量的所有權(quán),并且只能存在一個(gè)可變引用。 - ?
FnOnce
:表示閉包擁有自由變量的所有權(quán),并且只能被調(diào)用一次。這意味著,閉包必須擁有自由變量的所有權(quán),并且只能在調(diào)用之后使用它們。
在閉包類型之間進(jìn)行轉(zhuǎn)換是非常簡(jiǎn)單的。只需要在閉包的參數(shù)列表中添加相應(yīng)的trait限定,即可將閉包轉(zhuǎn)換為特定的類型。例如,如果我們有一個(gè)Fn
類型的閉包,但是需要將它轉(zhuǎn)換為FnMut
類型,只需要在參數(shù)列表中添加mut
關(guān)鍵字,如下所示:
fn main() {
let x = 3;
let y = 5;
let add = |a:i32, b:i32| a + b;
let mut add_mut = |a:i32, b:i32| {
let result = a + b;
println!("Result: {}", result);
result
};
let add_fn: fn(i32, i32) - > i32 = add;
let add_mut_fn: fn(i32, i32) - > i32 = add_mut;
let add_once_fn: fn(i32, i32) - > i32 = |a:i32, b:i32| a + b + 10;
println!("add_fn: {}", add_fn(x,y));
println!("add_mut_fn: {}", add_mut_fn(x,y));
println!("add_once_fn: {}", add_once_fn(x,y));
}
// 輸出結(jié)果:
// add_fn: 8
// Result: 8
// add_mut_fn: 8
// add_once_fn: 18
在上面的示例中,我們定義了三個(gè)閉包:add
、add_mut
和add_once
。add
和add_mut
都是Fn
類型的閉包,但是add_mut
使用了可變引用,因此它也是FnMut
類型閉包。我們使用fn
關(guān)鍵字將閉包轉(zhuǎn)換為函數(shù)類型,并指定參數(shù)和返回值的類型。在這種情況下,我們使用i32
作為參數(shù)和返回值的類型。
閉包的應(yīng)用與實(shí)踐
閉包在Rust語(yǔ)言中廣泛應(yīng)用于函數(shù)式編程、迭代器和多線程等領(lǐng)域。在函數(shù)式編程中,閉包常常用于實(shí)現(xiàn)高階函數(shù),如map()
、filter()
和reduce()
等。這些函數(shù)可以接受一個(gè)閉包作為參數(shù),然后對(duì)集合中的每個(gè)元素進(jìn)行轉(zhuǎn)換、過(guò)濾和歸約等操作。
以下是一個(gè)使用閉包實(shí)現(xiàn)map()
和filter()
函數(shù)的示例:
fn map< T, F >(source: Vec< T >, mut f: F) - > Vec >
where
F:Mut(T) - > T,
{
let mut result = Vec::new();
for item in source {
result.push(f(item));
}
result
}
fn filter< T, F >(source: Vec< T >, mut f: F) - > Vec< T >
where
F: FnMut(&T) - > bool,
{
let mut result = Vec::new();
for item in source {
if f(&item) {
result.push(item);
}
}
result
}
在上面的示例中,我們定義了map()
和filter()
函數(shù),它們接受一個(gè)閉包作為參數(shù),并對(duì)集合中的每個(gè)元素進(jìn)行轉(zhuǎn)換和過(guò)濾操作。map()
函數(shù)將集合中的每個(gè)元素傳遞給閉包進(jìn)行轉(zhuǎn)換,并將轉(zhuǎn)換后的結(jié)果收集到一個(gè)新的Vec
中。filter()
函數(shù)將集合中的每個(gè)元素傳遞給閉包進(jìn)行過(guò)濾,并將通過(guò)過(guò)濾的元素收集到一個(gè)新的Vec
中。
以下是一個(gè)使用閉包實(shí)現(xiàn)多線程的示例:
use std::thread;
fn main() {
let mut handles = Vec::new();
for i in 0..10 {
let handle = thread::spawn(move || {
println!("Thread {}: Hello, world!", i);
});
handles.push(handle);
}
for handle in handles {
handle.join().unwrap();
}
}
// 輸出結(jié)果:
// Thread 1: Hello, world!
// Thread 7: Hello, world!
// Thread 8: Hello, world!
// Thread 9: Hello, world!
// Thread 6: Hello, world!
// Thread 5: Hello, world!
// Thread 4: Hello, world!
// Thread 3: Hello, world!
// Thread 2: Hello, world!
// Thread 0: Hello, world!
在上面的示例中,我們使用thread::spawn()
函數(shù)創(chuàng)建了10個(gè)新線程,并使用閉包將每個(gè)線程的編號(hào)傳遞給它們。在閉包中,我們使用move
關(guān)鍵字將i
移動(dòng)到閉包內(nèi)部,以便在創(chuàng)建線程之后,i
的所有權(quán)被轉(zhuǎn)移 給了閉包。然后,我們將每個(gè)線程的句柄存儲(chǔ)在一個(gè)Vec
中,并使用join()
函數(shù)等待每個(gè)線程完成。
總結(jié)
Rust語(yǔ)言中的閉包是一種非常強(qiáng)大的特性,可以用于實(shí)現(xiàn)高階函數(shù)、函數(shù)式編程、迭代器和多線程等領(lǐng)域。閉包具有捕獲自由變量的能力,并且可以在閉包后繼續(xù)使用它們。在Rust語(yǔ)言中,閉包是一種特殊的類型,被稱為Fn
、FnMut
和Once
,用于區(qū)閉包的捕獲方式和參數(shù)類型。閉包可以通過(guò)實(shí)現(xiàn)這些trait來(lái)進(jìn)行類型轉(zhuǎn)換。
盡管閉包在Rust語(yǔ)言中非常強(qiáng)大和靈活,但是使用它們時(shí)需要謹(jǐn)慎。閉包的捕獲方式和參數(shù)類型可能會(huì)導(dǎo)致所有權(quán)和可變性的問(wèn)題,尤其是在多線程環(huán)境中。因此,我們應(yīng)該在使用閉包時(shí)仔細(xì)思考,并遵循Rust語(yǔ)言的所有權(quán)和可變性規(guī)則。
總之,閉包是一種非常有用的特性,可以幫助我們編寫更加靈活和高效的代碼。如果您還沒(méi)有使用過(guò)閉包,請(qǐng)嘗試在您的項(xiàng)目中使用它們,并體驗(yàn)閉包帶來(lái)的便利和效率。
-
存儲(chǔ)
+關(guān)注
關(guān)注
13文章
4337瀏覽量
85992 -
編程
+關(guān)注
關(guān)注
88文章
3633瀏覽量
93848 -
數(shù)據(jù)處理
+關(guān)注
關(guān)注
0文章
610瀏覽量
28599 -
rust語(yǔ)言
+關(guān)注
關(guān)注
0文章
57瀏覽量
3016 -
閉包
+關(guān)注
關(guān)注
0文章
4瀏覽量
2070
發(fā)布評(píng)論請(qǐng)先 登錄
相關(guān)推薦
評(píng)論