You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
trpl-zh-cn/src/ch19-05-advanced-functions-...

105 lines
4.5 KiB

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

## 高级函数与闭包
> [ch19-05-advanced-functions-and-closures.md](https://github.com/rust-lang/book/blob/master/second-edition/src/ch19-05-advanced-functions-and-closures.md)
> <br>
> commit d06a6a181fd61704cbf7feb55bc61d518c6469f9
最后让我们讨论一些有关函数和闭包的高级功能:函数指针、发散函数和返回值闭包。
### 函数指针
我们讨论过了如何向函数传递闭包,不过也可以向函数传递常规的函数!函数的类型是 `fn`,使用小写的 “f” 以便不与 `Fn` 闭包 trait 向混淆。`fn` 被称为**函数指针***function pointer*)。指定参数为函数指针的语法类似于闭包,如列表 19-34 所示:
<span class="filename">文件名: src/main.rs</span>
```rust
fn add_one(x: i32) -> i32 {
x + 1
}
fn do_twice(f: fn(i32) -> i32, arg: i32) -> i32 {
f(arg) + f(arg)
}
fn main() {
let answer = do_twice(add_one, 5);
println!("The answer is: {}", answer);
}
```
<span class="caption">列表 19-34使用 `fn` 类型接受函数指针作为参数</span>
这会打印出 `The answer is: 12`。`do_twice` 中的 `f` 被指定为一个接受一个 `i32` 参数并返回 `i32``fn`。接着就可以在 `do_twice` 函数体中调用 `f`。在 `main` 中,可以将函数名 `add_one` 作为第一个参数传递给 `do_twice`
不同于闭包,`fn` 是一个类型而不是一个 trait所以直接指定 `fn` 作为参数而不是声明一个带有 `Fn` 作为 trait bound 的泛型参数。
函数指针实现了所有三个闭包 trait`Fn`、`FnMut` 和 `FnOnce`),所以总是可以在调用期望闭包的函数时传递函数指针作为参数。倾向于编写使用泛型和闭包 trait 的函数,这样它就能接受函数或闭包作为参数。一个只期望接受 `fn` 的情况的例子是与不存在闭包的外部代码交互时C 语言的函数可以接受函数作为参数,但没有闭包。
比如,如果希望使用 `map` 函数将一个数字 vector 转换为一个字符串 vector就可以使用闭包
```rust
let list_of_numbers = vec![1, 2, 3];
let list_of_strings: Vec<String> = list_of_numbers
.iter()
.map(|i| i.to_string())
.collect();
```
或者可以将函数作为 `map` 的参数来代替闭包:
```rust
let list_of_numbers = vec![1, 2, 3];
let list_of_strings: Vec<String> = list_of_numbers
.iter()
.map(ToString::to_string)
.collect();
```
注意这里必须使用“高级 trait”部分讲到的完全限定语法因为存在多个叫做 `to_string` 的函数;这里使用定义于 `ToString` trait 的 `to_string` 函数,标准库为所有实现了 `Display` 的类型实现了这个 trait。
一些人倾向于函数风格,一些人喜欢闭包。他们最终都会产生同样的代码,所以请使用你更明白的吧。
### 返回闭包
因为闭包以 trait 的形式体现,返回闭包就有点微妙了;不能直接这么做。对于大部分需要返回 trait 的情况,可以使用是实现了期望返回的 trait 的具体类型替代函数的返回值。但是这不能用于闭包。他们没有一个可返回的具体类型;例如不允许使用函数指针 `fn` 作为返回值类型。
这段代码尝试直接返回闭包,它并不能编译:
```rust
fn returns_closure() -> Fn(i32) -> i32 {
|x| x + 1
}
```
编译器给出的错误是:
```
error[E0277]: the trait bound `std::ops::Fn(i32) -> i32 + 'static:
std::marker::Sized` is not satisfied
--> <anon>:2:25
|
2 | fn returns_closure() -> Fn(i32) -> i32 {
| ^^^^^^^^^^^^^^ the trait `std::marker::Sized` is
not implemented for `std::ops::Fn(i32) -> i32 + 'static`
|
= note: `std::ops::Fn(i32) -> i32 + 'static` does not have a constant size
known at compile-time
= note: the return type of a function must have a statically known size
```
又是 `Sized` traitRust 并不知道需要多少空间来储存闭包。不过我们在上一部分见过这种情况的解决办法:可以使用 trait 对象:
```rust
fn returns_closure() -> Box<Fn(i32) -> i32> {
Box::new(|x| x + 1)
}
```
关于 trait 对象的更多内容,请参考第十八章。
## 总结
好的!现在我们学习了 Rust 并不常用但你可能用得着的功能。我们介绍了很多复杂的主题,这样当你在错误信息提示或阅读他人代码时遇到他们时,至少可以说已经见过这些概念和语法了。
现在,让我们再开始一个项目,将本书所学的所有内容付与实践!