|
|
@ -1,6 +1,6 @@
|
|
|
|
# 闭包closure
|
|
|
|
# 闭包closure
|
|
|
|
|
|
|
|
|
|
|
|
闭包这个词语由来已久,自上世纪60年代就由 `Scheme` 语言引进之后,被广泛用于函数式编程语言中,进入21世纪后,各种现代化的编程语言也都不约而同地把闭包作为核心特性纳入到语言设计中来。那么到底何为闭包?
|
|
|
|
闭包这个词语由来已久,自上世纪 60 年代就由 `Scheme` 语言引进之后,被广泛用于函数式编程语言中,进入 21 世纪后,各种现代化的编程语言也都不约而同地把闭包作为核心特性纳入到语言设计中来。那么到底何为闭包?
|
|
|
|
|
|
|
|
|
|
|
|
闭包是**一种匿名函数,它可以赋值给变量也可以作为参数传递给其它函数,不同于函数的是,它允许捕获调用者作用域中的值**,例如:
|
|
|
|
闭包是**一种匿名函数,它可以赋值给变量也可以作为参数传递给其它函数,不同于函数的是,它允许捕获调用者作用域中的值**,例如:
|
|
|
|
```rust
|
|
|
|
```rust
|
|
|
@ -12,7 +12,7 @@ fn main() {
|
|
|
|
}
|
|
|
|
}
|
|
|
|
```
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
上面的代码展示了非常简单的闭包 `sum`,它拥有一个入参 `y`,同时捕获了作用域中的 `x` 的值,因此调用 `sum(2)` 意味着将 2(参数 `y`) 跟 1(`x`)进行相加,最终返回它们的和:`3`。
|
|
|
|
上面的代码展示了非常简单的闭包 `sum`,它拥有一个入参 `y`,同时捕获了作用域中的 `x` 的值,因此调用 `sum(2)` 意味着将 2(参数 `y`)跟 1(`x`)进行相加,最终返回它们的和:`3`。
|
|
|
|
|
|
|
|
|
|
|
|
可以看到 `sum` 非常符合闭包的定义:可以赋值给变量,允许捕获调用者作用域中的值。
|
|
|
|
可以看到 `sum` 非常符合闭包的定义:可以赋值给变量,允许捕获调用者作用域中的值。
|
|
|
|
|
|
|
|
|
|
|
@ -41,15 +41,13 @@ fn workout(intensity: u32, random_number: u32) {
|
|
|
|
"旁边有妹子在看,俯卧撑太low,再来 {} 组卧推!",
|
|
|
|
"旁边有妹子在看,俯卧撑太low,再来 {} 组卧推!",
|
|
|
|
muuuuu(intensity)
|
|
|
|
muuuuu(intensity)
|
|
|
|
);
|
|
|
|
);
|
|
|
|
|
|
|
|
} else if random_number == 3 {
|
|
|
|
|
|
|
|
println!("昨天练过度了,今天还是休息下吧!");
|
|
|
|
} else {
|
|
|
|
} else {
|
|
|
|
if random_number == 3 {
|
|
|
|
println!(
|
|
|
|
println!("昨天练过度了,今天还是休息下吧!");
|
|
|
|
"昨天练过度了,今天干干有氧,跑步 {} 分钟!",
|
|
|
|
} else {
|
|
|
|
muuuuu(intensity)
|
|
|
|
println!(
|
|
|
|
);
|
|
|
|
"昨天练过度了,今天干干有氧,跑步 {} 分钟!",
|
|
|
|
|
|
|
|
muuuuu(intensity)
|
|
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
@ -64,11 +62,21 @@ fn main() {
|
|
|
|
}
|
|
|
|
}
|
|
|
|
```
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
可以看到,在健身时我们根据想要的强度来调整具体的动作,然后调用 `muuuuu` 函数来开始健身。这个程序本身很简单,没啥好说的,但是假如未来不用 `muuuu` 函数了,是不是得把所有 `muuuu` 都替换成,比如说 `woooo` ?如果 `muuuu` 出现了几十次,那意味着我们要修改几十处地方。
|
|
|
|
可以看到,在健身时我们根据想要的强度来调整具体的动作,然后调用 `muuuuu` 函数来开始健身。这个程序本身很简单,没啥好说的,但是假如未来不用 `muuuuu` 函数了,是不是得把所有 `muuuuu` 都替换成,比如说 `woooo` ?如果 `muuuuu` 出现了几十次,那意味着我们要修改几十处地方。
|
|
|
|
|
|
|
|
|
|
|
|
#### 函数变量实现
|
|
|
|
#### 函数变量实现
|
|
|
|
一个可行的办法是,把函数赋值给一个变量,然后通过变量调用:
|
|
|
|
一个可行的办法是,把函数赋值给一个变量,然后通过变量调用:
|
|
|
|
```rust
|
|
|
|
```rust
|
|
|
|
|
|
|
|
use std::thread;
|
|
|
|
|
|
|
|
use std::time::Duration;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 开始健身,好累,我得发出声音:muuuu...
|
|
|
|
|
|
|
|
fn muuuuu(intensity: u32) -> u32 {
|
|
|
|
|
|
|
|
println!("muuuu.....");
|
|
|
|
|
|
|
|
thread::sleep(Duration::from_secs(2));
|
|
|
|
|
|
|
|
intensity
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
fn workout(intensity: u32, random_number: u32) {
|
|
|
|
fn workout(intensity: u32, random_number: u32) {
|
|
|
|
let action = muuuuu;
|
|
|
|
let action = muuuuu;
|
|
|
|
if intensity < 25 {
|
|
|
|
if intensity < 25 {
|
|
|
@ -80,17 +88,25 @@ fn workout(intensity: u32, random_number: u32) {
|
|
|
|
"旁边有妹子在看,俯卧撑太low, 再来 {} 组卧推!",
|
|
|
|
"旁边有妹子在看,俯卧撑太low, 再来 {} 组卧推!",
|
|
|
|
action(intensity)
|
|
|
|
action(intensity)
|
|
|
|
);
|
|
|
|
);
|
|
|
|
|
|
|
|
} else if random_number == 3 {
|
|
|
|
|
|
|
|
println!("昨天练过度了,今天还是休息下吧!");
|
|
|
|
} else {
|
|
|
|
} else {
|
|
|
|
if random_number == 3 {
|
|
|
|
println!(
|
|
|
|
println!("昨天练过度了,今天还是休息下吧!");
|
|
|
|
"昨天练过度了,今天干干有氧, 跑步 {} 分钟!",
|
|
|
|
} else {
|
|
|
|
action(intensity)
|
|
|
|
println!(
|
|
|
|
);
|
|
|
|
"昨天练过度了,今天干干有氧, 跑步 {} 分钟!",
|
|
|
|
|
|
|
|
action(intensity)
|
|
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
fn main() {
|
|
|
|
|
|
|
|
// 强度
|
|
|
|
|
|
|
|
let intensity = 10;
|
|
|
|
|
|
|
|
// 随机值用来决定某个选择
|
|
|
|
|
|
|
|
let random_number = 7;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 开始健身
|
|
|
|
|
|
|
|
workout(intensity, random_number);
|
|
|
|
|
|
|
|
}
|
|
|
|
```
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@ -104,6 +120,9 @@ fn workout(intensity: u32, random_number: u32) {
|
|
|
|
|
|
|
|
|
|
|
|
上面提到 `intensity` 要是变化怎么办,简单,使用闭包来捕获它,这是我们的拿手好戏:
|
|
|
|
上面提到 `intensity` 要是变化怎么办,简单,使用闭包来捕获它,这是我们的拿手好戏:
|
|
|
|
```rust
|
|
|
|
```rust
|
|
|
|
|
|
|
|
use std::thread;
|
|
|
|
|
|
|
|
use std::time::Duration;
|
|
|
|
|
|
|
|
|
|
|
|
fn workout(intensity: u32, random_number: u32) {
|
|
|
|
fn workout(intensity: u32, random_number: u32) {
|
|
|
|
let action = || {
|
|
|
|
let action = || {
|
|
|
|
println!("muuuu.....");
|
|
|
|
println!("muuuu.....");
|
|
|
@ -120,15 +139,13 @@ fn workout(intensity: u32, random_number: u32) {
|
|
|
|
"旁边有妹子在看,俯卧撑太low,再来 {} 组卧推!",
|
|
|
|
"旁边有妹子在看,俯卧撑太low,再来 {} 组卧推!",
|
|
|
|
action()
|
|
|
|
action()
|
|
|
|
);
|
|
|
|
);
|
|
|
|
|
|
|
|
} else if random_number == 3 {
|
|
|
|
|
|
|
|
println!("昨天练过度了,今天还是休息下吧!");
|
|
|
|
} else {
|
|
|
|
} else {
|
|
|
|
if random_number == 3 {
|
|
|
|
println!(
|
|
|
|
println!("昨天练过度了,今天还是休息下吧!");
|
|
|
|
"昨天练过度了,今天干干有氧,跑步 {} 分钟!",
|
|
|
|
} else {
|
|
|
|
action()
|
|
|
|
println!(
|
|
|
|
);
|
|
|
|
"昨天练过度了,今天干干有氧,跑步 {} 分钟!",
|
|
|
|
|
|
|
|
action()
|
|
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
@ -167,7 +184,7 @@ Rust 闭包在形式上借鉴了 `Smalltalk` 和 `Ruby` 语言,与函数最大
|
|
|
|
## 闭包的类型推导
|
|
|
|
## 闭包的类型推导
|
|
|
|
Rust 是静态语言,因此所有的变量都具有类型,但是得益于编译器的强大类型推导能力,在很多时候我们并不需要显式地去声明类型,但是显然函数并不在此列,必须手动为函数的所有参数和返回值指定类型,原因在于函数往往会作为 API 提供给你的用户,因此你的用户必须在使用时知道传入参数的类型和返回值类型。
|
|
|
|
Rust 是静态语言,因此所有的变量都具有类型,但是得益于编译器的强大类型推导能力,在很多时候我们并不需要显式地去声明类型,但是显然函数并不在此列,必须手动为函数的所有参数和返回值指定类型,原因在于函数往往会作为 API 提供给你的用户,因此你的用户必须在使用时知道传入参数的类型和返回值类型。
|
|
|
|
|
|
|
|
|
|
|
|
与函数相反,闭包并不会作为API对外提供,因此它可以享受编译器的类型推导能力,无需标注参数和返回值的类型。
|
|
|
|
与函数相反,闭包并不会作为 API 对外提供,因此它可以享受编译器的类型推导能力,无需标注参数和返回值的类型。
|
|
|
|
|
|
|
|
|
|
|
|
为了增加代码可读性,有时候我们会显式地给类型进行标注,出于同样的目的,也可以给闭包标注类型:
|
|
|
|
为了增加代码可读性,有时候我们会显式地给类型进行标注,出于同样的目的,也可以给闭包标注类型:
|
|
|
|
```rust
|
|
|
|
```rust
|
|
|
@ -176,10 +193,10 @@ let sum = |x: i32, y: i32| -> i32 {
|
|
|
|
}
|
|
|
|
}
|
|
|
|
```
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
与之相比,不标注类型的闭包声明会更简洁些:`let sum = |x, y| x + y`,需要注意的是,针对 `sum` 闭包,如果你只进行了声明,但是没有使用,编译器会提示你为 `x,y` 添加类型标注,因为它缺乏必要的上下文:
|
|
|
|
与之相比,不标注类型的闭包声明会更简洁些:`let sum = |x, y| x + y`,需要注意的是,针对 `sum` 闭包,如果你只进行了声明,但是没有使用,编译器会提示你为 `x, y` 添加类型标注,因为它缺乏必要的上下文:
|
|
|
|
```rust
|
|
|
|
```rust
|
|
|
|
let sum = |x, y| x + y;
|
|
|
|
let sum = |x, y| x + y;
|
|
|
|
let v = sum(1,2);
|
|
|
|
let v = sum(1, 2);
|
|
|
|
```
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
这里我们使用了 `sum`,同时把 `1` 传给了 `x`,`2` 传给了 `y`,因此编译器才可以推导出 `x,y` 的类型为 `i32`。
|
|
|
|
这里我们使用了 `sum`,同时把 `1` 传给了 `x`,`2` 传给了 `y`,因此编译器才可以推导出 `x,y` 的类型为 `i32`。
|
|
|
@ -274,7 +291,7 @@ where
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
## 捕获作用域中的值
|
|
|
|
## 捕获作用域中的值
|
|
|
|
在之前代码中,我们一直在用闭包的匿名函数特性(赋值给变量),然而闭包还拥有一项函数所不具备的特性:捕获作用域中的值。
|
|
|
|
在之前代码中,我们一直在用闭包的匿名函数特性(赋值给变量),然而闭包还拥有一项函数所不具备的特性:捕获作用域中的值。
|
|
|
|
```rust
|
|
|
|
```rust
|
|
|
|
fn main() {
|
|
|
|
fn main() {
|
|
|
|
let x = 4;
|
|
|
|
let x = 4;
|
|
|
|