mirror of
https://github.com/KaiserY/trpl-zh-cn
synced 2024-11-09 00:43:59 +08:00
check to ch20-00
This commit is contained in:
parent
06c418f8df
commit
cdfbb9d17a
@ -2,25 +2,31 @@
|
||||
|
||||
> [ch19-04-advanced-types.md](https://github.com/rust-lang/book/blob/master/second-edition/src/ch19-04-advanced-types.md)
|
||||
> <br>
|
||||
> commit e084e1773667c8eae28d9aab6d4939348eec0092
|
||||
> commit 9d5b9a573daf5fa0c98b3a3005badcea4a0a5211
|
||||
|
||||
Rust 的类型系统有一些我们曾经提到或用到但没有讨论过的功能。我们从有关 trait 的 newtype 模式开始讨论;首先从一个关于为什么 newtype 与类型一样有用的更宽泛的讨论开始。接着会转向类型别名(type aliases),一个类似于 newtype 但有着稍微不同的语义的功能。我们还会讨论 `!` 类型和动态大小类型。
|
||||
Rust 的类型系统有一些我们曾经提到但没有讨论过的功能。首先我们从一个关于为什么 newtype 与类型一样有用的更宽泛的讨论开始。接着会转向类型别名(type aliases),一个类似于 newtype 但有着稍微不同的语义的功能。我们还会讨论 `!` 类型和动态大小类型。
|
||||
|
||||
### 为了类型安全和抽象而使用 newtype 模式
|
||||
|
||||
在“高级 trait”部分最后开始的 newtype 模式的讨论中,我们以一个包含一个封装了某类型的字段的元组结构体创建了一个新类型,这对于静态的确保其值不被混淆也是有帮助的,并且它经常用来表示一个值的单元。实际上列表 19-26 中已有一个例子:`Millimeters` 和 `Meters` 结构体都将 `u32` 值封装进了新类型。如果编写了一个有 `Millimeters` 类型参数的函数,不小心使用 `Meters` 或普通的 `u32` 值来调用该函数的程序是不能编译的。
|
||||
> 这一部分假设你已经阅读了 “高级 trait” 部分的 newtype 模式相关内容。
|
||||
|
||||
另一个使用 newtype 模式的原因是用来抽象掉一些类型的实现细节:例如,封装类型可以暴露出与直接使用其内部私有类型时所不同的 API,以便限制其功能。新类型也可以隐藏其内部的泛型类型。例如,可以提供一个封装了 `HashMap<i32, String>` 的 `People` 类型,用来储存人名以及相应的 ID。使用 `People` 的代码只需与提供的公有 API 交互即可,比如向 `People` 集合增加名字字符串的方法,这样这些代码就无需知道在内部我们将一个 `i32` ID 赋予了这个名字了。newtype 模式是一种实现第十七章所讨论的隐藏实现细节的封装的轻量级方法。
|
||||
newtype 模式可以用于一些其他我们还未讨论的功能,包括静态的确保某值不被混淆,和用来表示一个值的单元。实际上示例 19-23 中已经有一个这样的例子:`Millimeters` 和 `Meters` 结构体都在 newtype 中封装了 `u32` 值。如果编写了一个有 `Millimeters` 类型参数的函数,不小心使用 `Meters` 或普通的 `u32` 值来调用该函数的程序是不能编译的。
|
||||
|
||||
另一个 newtype 模式的应用在于抽象掉一些类型的实现细节:例如,封装类型可以暴露出与直接使用其内部私有类型时所不同的 API,以便限制其功能。
|
||||
|
||||
|
||||
|
||||
新类型也可以隐藏其内部的泛型类型。例如,可以提供一个封装了 `HashMap<i32, String>` 的 `People` 类型,用来储存人名以及相应的 ID。使用 `People` 的代码只需与提供的公有 API 交互即可,比如向 `People` 集合增加名字字符串的方法,这样这些代码就无需知道在内部我们将一个 `i32` ID 赋予了这个名字了。newtype 模式是一种实现第十七章 “封装隐藏了实现细节” 部分所讨论的隐藏实现细节的封装的轻量级方法。
|
||||
|
||||
### 类型别名用来创建同义类型
|
||||
|
||||
newtype 模式涉及到创建新结构体来作为新的、单独的类型。Rust 还提供了声明**类型别名**(*type alias*)的能力,使用 `type` 关键字来给予现有类型另一个名字。例如,可以像这样创建 `i32` 的别名 `Kilometers`:
|
||||
连同 newtype 模式,Rust 还提供了声明 **类型别名**(*type alias*)的能力,使用 `type` 关键字来给予现有类型另一个名字。例如,可以像这样创建 `i32` 的别名 `Kilometers`:
|
||||
|
||||
```rust
|
||||
type Kilometers = i32;
|
||||
```
|
||||
|
||||
这意味着 `Kilometers` 是 `i32` 的**同义词**(*synonym*);不同于列表 19-26 中创建的 `Millimeters` 和 `Meters` 类型。`Kilometers` 不是一个新的、单独的类型。`Kilometers` 类型的值将被完全当作 `i32` 类型值来对待:
|
||||
这意味着 `Kilometers` 是 `i32` 的 **同义词**(*synonym*);不同于示例 19-23 中创建的 `Millimeters` 和 `Meters` 类型。`Kilometers` 不是一个新的、单独的类型。`Kilometers` 类型的值将被完全当作 `i32` 类型值来对待:
|
||||
|
||||
```rust
|
||||
type Kilometers = i32;
|
||||
@ -31,53 +37,53 @@ let y: Kilometers = 5;
|
||||
println!("x + y = {}", x + y);
|
||||
```
|
||||
|
||||
因为 `Kilometers` 是 `i32` 的别名,他们是同一类型。可以将 `i32` 与 `Kilometers` 相加,可以将 `Kilometers` 传递给获取 `i32` 参数的函数。但无法获得上一部分讨论的 newtype 模式所提供的类型检查的好处。
|
||||
因为 `Kilometers` 是 `i32` 的别名,他们是同一类型,可以将 `i32` 与 `Kilometers` 相加,也可以将 `Kilometers` 传递给获取 `i32` 参数的函数。但通过这种手段无法获得上一部分讨论的 newtype 模式所提供的类型检查的好处。
|
||||
|
||||
类型别名的主要用途是减少重复。例如,可能会有这样很长的类型:
|
||||
|
||||
```rust
|
||||
Box<FnOnce() + Send + 'static>
|
||||
```rust,ignore
|
||||
Box<Fn() + Send + 'static>
|
||||
```
|
||||
|
||||
在函数签名或类型注解中每次都书写这个类型将是枯燥且易于出错的。想象一下如列表 19-31 这样全是如此代码的项目:
|
||||
在函数签名或类型注解中每次都书写这个类型将是枯燥且易于出错的。想象一下如示例 19-32 这样全是如此代码的项目:
|
||||
|
||||
```rust
|
||||
let f: Box<FnOnce() + Send + 'static> = Box::new(|| println!("hi"));
|
||||
let f: Box<Fn() + Send + 'static> = Box::new(|| println!("hi"));
|
||||
|
||||
fn takes_long_type(f: Box<FnOnce() + Send + 'static>) {
|
||||
// ...
|
||||
fn takes_long_type(f: Box<Fn() + Send + 'static>) {
|
||||
// --snip--
|
||||
}
|
||||
|
||||
fn returns_long_type() -> Box<FnOnce() + Send + 'static> {
|
||||
// ...
|
||||
fn returns_long_type() -> Box<Fn() + Send + 'static> {
|
||||
// --snip--
|
||||
# Box::new(|| ())
|
||||
}
|
||||
```
|
||||
|
||||
<span class="caption">列表 19-31:在大部分地方使用名称很长的类型</span>
|
||||
<span class="caption">示例 19-32: 在很多地方使用名称很长的类型</span>
|
||||
|
||||
类型别名通过减少项目中重复代码的数量来使其更加易于控制。这里我们为这个冗长的类型引入了一个叫做 `Thunk` 的别名,这样就可以如列表 19-32 所示将所有使用这个类型的地方替换为更短的 `Thunk`:
|
||||
类型别名通过减少项目中重复代码的数量来使其更加易于控制。这里我们为这个冗长的类型引入了一个叫做 `Thunk` 的别名,这样就可以如示例 19-33 所示将所有使用这个类型的地方替换为更短的 `Thunk`:
|
||||
|
||||
```rust
|
||||
type Thunk = Box<FnOnce() + Send + 'static>;
|
||||
type Thunk = Box<Fn() + Send + 'static>;
|
||||
|
||||
let f: Thunk = Box::new(|| println!("hi"));
|
||||
|
||||
fn takes_long_type(f: Thunk) {
|
||||
// ...
|
||||
// --snip--
|
||||
}
|
||||
|
||||
fn returns_long_type() -> Thunk {
|
||||
// ...
|
||||
// --snip--
|
||||
# Box::new(|| ())
|
||||
}
|
||||
```
|
||||
|
||||
<span class="caption">列表 19-32:引入类型别名 `Thunk` 来减少重复</span>
|
||||
<span class="caption">示例 19-33: 引入类型别名 `Thunk` 来减少重复</span>
|
||||
|
||||
这样就读写起来就容易多了!为类型别名选择一个好名字也可以帮助你表达意图(单词 *thunk* 表示会在之后被计算的代码,所以这是一个存放闭包的合适的名字)。
|
||||
|
||||
类型别名的另一个常用用法是与 `Result<T, E>` 结合。考虑一下标准库中的 `std::io` 模块。I/O 操作通常会返回一个 `Result<T, E>`,因为这些操作可能会失败。`std::io::Error` 结构体代表了所有可能的 I/O 错误。`std::io` 中大部分函数会返回 `Result<T, E>`,其中 `E` 是 `std::io::Error`,比如 `Write` trait 中的这些函数:
|
||||
类型别名也经常与 `Result<T, E>` 结合使用来减少重复。考虑一下标准库中的 `std::io` 模块。I/O 操作通常会返回一个 `Result<T, E>`,因为这些操作可能会失败。标准库中的 `std::io::Error` 结构体代表了所有可能的 I/O 错误。`std::io` 中大部分函数会返回 `Result<T, E>`,其中 `E` 是 `std::io::Error`,比如 `Write` trait 中的这些函数:
|
||||
|
||||
```rust
|
||||
use std::io::Error;
|
||||
@ -94,13 +100,13 @@ pub trait Write {
|
||||
|
||||
这里出现了很多的 `Result<..., Error>`。为此,`std::io` 有这个类型别名声明:
|
||||
|
||||
```rust
|
||||
```rust,ignore
|
||||
type Result<T> = Result<T, std::io::Error>;
|
||||
```
|
||||
|
||||
因为这位于 `std::io` 中,可用的完全限定的别名是 `std::io::Result<T>`;也就是说,`Result<T, E>` 中 `E` 放入了 `std::io::Error`。`Write` trait 中的函数最终看起来像这样:
|
||||
|
||||
```rust
|
||||
```rust,ignore
|
||||
pub trait Write {
|
||||
fn write(&mut self, buf: &[u8]) -> Result<usize>;
|
||||
fn flush(&mut self) -> Result<()>;
|
||||
@ -116,11 +122,16 @@ pub trait Write {
|
||||
|
||||
Rust 有一个叫做 `!` 的特殊类型。在类型理论术语中,它被称为 *empty type*,因为它没有值。我们更倾向于称之为 *never type*。这个名字描述了它的作用:在函数从不返回的时候充当返回值。例如:
|
||||
|
||||
```rust
|
||||
```rust,ignore
|
||||
fn bar() -> ! {
|
||||
// --snip--
|
||||
}
|
||||
```
|
||||
|
||||
这读作“函数 `bar` 从不返回”,而从不返回的函数被称为**发散函数**(*diverging functions*)。不能创建 `!` 类型的值,所以 `bar` 也不可能返回。一个不能创建值的类型有什么用呢?如果你回想一下第二章,曾经有一些看起来像这样的代码,如列表 19-33 所重现的:
|
||||
这读 “函数 `bar` 从不返回”,而从不返回的函数被称为 **发散函数**(*diverging functions*)。不能创建 `!` 类型的值,所以 `bar` 也不可能返回。
|
||||
|
||||
|
||||
不过一个不能创建值的类型有什么用呢?如果你回想一下第二章,曾经有一些看起来像这样的代码,如示例 19-34 所重现的:
|
||||
|
||||
```rust
|
||||
# let guess = "3";
|
||||
@ -133,24 +144,34 @@ let guess: u32 = match guess.trim().parse() {
|
||||
# }
|
||||
```
|
||||
|
||||
<span class="caption">列表 19-33:`match` 语句和一个以 `continue` 结束的分支</span>
|
||||
<span class="caption">示例 19-34: `match` 语句和一个以 `continue` 结束的分支</span>
|
||||
|
||||
当时我们忽略了一些代码细节。在第六章中,我们学习了 `match` 的分支必须返回相同的类型。如下代码不能工作:
|
||||
当时我们忽略了代码中的一些细节。在第六章 “`match` 控制流运算符” 部分,我们学习了 `match` 的分支必须返回相同的类型。如下代码不能工作:
|
||||
|
||||
```rust
|
||||
```rust,ignore
|
||||
let guess = match guess.trim().parse() {
|
||||
Ok(_) => 5,
|
||||
Err(_) => "hello",
|
||||
}
|
||||
```
|
||||
|
||||
这里的 `guess` 会是什么类型呢?它必须既是整型也是字符串,而 Rust 要求 `guess` 只能是一个类型。那么 `continue` 返回了什么呢?为什么列表 19-33 中会允许一个分支返回 `u32` 而另一个分支却以 `continue` 结束呢?
|
||||
这里的 `guess` 必须既是整型也是字符串,而 Rust 要求 `guess` 只能是一个类型。那么 `continue` 返回了什么呢?为什么示例 19-34 中会允许一个分支返回 `u32` 而另一个分支却以 `continue` 结束呢?
|
||||
|
||||
正如你可能猜到的,`continue` 的值是 `!`。也就是说,当 Rust 要计算 `guess` 的类型时,它查看这两个分支。前者是 `u32` 值,而后者是 `!` 值。因为 `!` 并没有一个值,Rust 认为这是可行的,并决定 `guess` 的类型是 `u32`。描述 `!` 的行为的正式方式是 never type 可以与其他任何类型联合。允许 `match` 的分支以 `continue` 结束是因为 `continue` 并不真正返回一个值;相反它把控制权交回上层循环,所以在 `Err` 的情况,事实上并未对 `guess` 赋值。
|
||||
正如你可能猜到的,`continue` 的值是 `!`。也就是说,当 Rust 要计算 `guess` 的类型时,它查看这两个分支。前者是 `u32` 值,而后者是 `!` 值。因为 `!` 并没有一个值,Rust 决定 `guess` 的类型是 `u32`。
|
||||
|
||||
描述 `!` 的行为的正式方式是 never type 可以强转为任何其他类型。允许 `match` 的分支以 `continue` 结束是因为 `continue` 并不真正返回一个值;相反它把控制权交回上层循环,所以在 `Err` 的情况,事实上并未对 `guess` 赋值。
|
||||
|
||||
<!-- I'm not sure I'm following what would then occur in the event of an error,
|
||||
literally nothing? -->
|
||||
<!-- The block returns control to the enclosing loop; I'm not sure how to
|
||||
clarify this other than what we already have here, do you have any suggestions?
|
||||
I wouldn't say it's "literally nothing" because it does do something, it
|
||||
returns control to the loop and the next iteration of the loop happens...
|
||||
/Carol -->
|
||||
|
||||
never type 的另一个用途是 `panic!`。还记得 `Option<T>` 上的 `unwrap` 函数吗?它产生一个值或 panic。这里是它的定义:
|
||||
|
||||
```rust
|
||||
```rust,ignore
|
||||
impl<T> Option<T> {
|
||||
pub fn unwrap(self) -> T {
|
||||
match self {
|
||||
@ -161,11 +182,11 @@ impl<T> Option<T> {
|
||||
}
|
||||
```
|
||||
|
||||
这里与列表 19-33 中的 `match` 发生的相同的情况:我们知道 `val` 是 `T` 类型,`panic!` 是 `!` 类型,所以整个 `match` 表达式的结果是 `T` 类型。这能工作是因为 `panic!` 并不产生一个值:它终止程序。对于 `None` 的情况,`unwrap` 并不返回一个值,所以这些代码是有效。
|
||||
这里与示例 19-34 中的 `match` 发生了相同的情况:我们知道 `val` 是 `T` 类型,`panic!` 是 `!` 类型,所以整个 `match` 表达式的结果是 `T` 类型。这能工作是因为 `panic!` 并不产生一个值;它会终止程序。对于 `None` 的情况,`unwrap` 并不返回一个值,所以这些代码是有效。
|
||||
|
||||
最后的表达式在 `loop` 中使用了 `!` 类型:
|
||||
最后一个有着 `!` 类型的表达式是 `loop`:
|
||||
|
||||
```rust
|
||||
```rust,ignore
|
||||
print!("forever ");
|
||||
|
||||
loop {
|
||||
@ -177,49 +198,62 @@ loop {
|
||||
|
||||
### 动态大小类型和 `Sized` trait
|
||||
|
||||
因为 Rust 需要知道类似内存布局之类的信息,在其类型系统的一个特定的角落可能令人迷惑,这就是**动态大小类型**(*dynamically sized types*)的概念。这有时被称为“DST” 或 “unsized types”,这些类型允许我们处理只有在运行时才知道大小的类型。
|
||||
因为 Rust 需要知道例如应该为特定类型的值分配多少空间这样的信息其类型系统的一个特定的角落可能令人迷惑:这就是 **动态大小类型**(*dynamically sized types*)的概念。这有时被称为 “DST” 或 “unsized types”,这些类型允许我们处理只有在运行时才知道大小的类型。
|
||||
|
||||
让我们深入研究一个贯穿本书都在使用的动态大小类型的细节:`str`。没错,不是 `&str`,而是 `str` 本身。`str` 是一个 DST;直到运行时我们都不知道字符串有多长。因为不能知道大小,也就不能创建 `str` 类型的变量,也不能获取 `str` 类型的参数。考虑一下这些代码,他们不能工作:
|
||||
让我们深入研究一个贯穿本书都在使用的动态大小类型的细节:`str`。没错,不是 `&str`,而是 `str` 本身。`str` 是一个 DST;直到运行时我们都不知道字符串有多长。因为直到运行时都不能知道大其小,也就意味着不能创建 `str` 类型的变量,也不能获取 `str` 类型的参数。考虑一下这些代码,他们不能工作:
|
||||
|
||||
```rust
|
||||
```rust,ignore
|
||||
let s1: str = "Hello there!";
|
||||
let s2: str = "How's it going?";
|
||||
```
|
||||
|
||||
这两个 `str` 值需要有完全一样的内存布局,不过他们却有不同的长度:`s1` 需要 12 字节来存储,而 `s2` 需要 15 字节。这样就是为为什么不可能创建一个存放动态大小类型的变量。
|
||||
<!-- Why do they need to have the same memory layout? Perhaps I'm not
|
||||
understanding fully what is meant by the memory layout, is it worth explaining
|
||||
that a little in this section? -->
|
||||
<!-- I've reworded /Carol -->
|
||||
|
||||
那么该怎么办呢?好吧,在这个例子中你已经知道了答案:`s1` 和 `s2` 的类型是 `&str` 而不是 `str`。如果你回想第四章,我们这样描述 `&str`:
|
||||
Rust 需要知道应该为特定类型的值分配多少内存,同时所有同一类型的值必须使用相同数量的内存。如果允许编写这样的代码,也就意味着这两个 `str` 需要占用完全相同大小的空间,不过它们有着不同的长度。这也就是为什么不可能创建一个存放动态大小类型的变量的原因。
|
||||
|
||||
> ... 这是一个字符串内部位置和其所引用的元素的数量的引用。
|
||||
那么该怎么办呢?你已经知道了这种问题的答案:`s1` 和 `s2` 的类型是 `&str` 而不是 `str`。如果你回想第四章 “字符串 slice” 部分,slice 数据结储存了开始位置和 slice 的长度。
|
||||
|
||||
所以虽然 `&T` 是一个储存了 `T` 所在的内存位置的单个值,`&str` 则是 **两个** 值:`str` 的地址和其长度。这样,`&str` 就有了一个在编译时可以知道的大小:它是 `usize` 长度的两倍。也就是说,我们总是知道 `&str` 的大小,而无论其引用的字符串是多长。这里是 Rust 中动态大小类型的常规用法:他们有一些额外的元信息来储存动态信息的大小。这引出了动态大小类型的黄金规则:必须将动态大小类型的值置于某种指针之后。
|
||||
|
||||
<!-- Note for Carol: `Rc<str>` is only in an accepted RFC right now, check on
|
||||
its progress and pull this out if it's not going to be stable by Oct -->
|
||||
|
||||
虽然我们总是说 `&str`,但是可以将 `str` 与所有类型的指针结合:比如 `Box<str>` 或 `Rc<str>`。事实上,之前已经见过了,不过是另一个动态大小类型:trait。每一个 trait 都是一个可以通过 trait 名称来引用的动态大小类型。在第十七章中,我们提到了为了将 trait 用于 trait 对象,必须将他们放入指针之后,比如 `&Trait` 或 `Box<Trait>`(`Rc<Trait>` 也可以)。
|
||||
可以将 `str` 与所有类型的指针结合:比如 `Box<str>` 或 `Rc<str>`。事实上,之前我们已经见过了,不过是另一个动态大小类型:trait。每一个 trait 都是一个可以通过 trait 名称来引用的动态大小类型。在第十七章 “为使用不同类型的值而设计的 trait 对象” 部分,我们提到了为了将 trait 用于 trait 对象,必须将他们放入指针之后,比如 `&Trait` 或 `Box<Trait>`(`Rc<Trait>` 也可以)。trait 之所以是动态大小类型的原因是必须这样才能这样使用它。
|
||||
|
||||
#### `Sized` trait
|
||||
|
||||
为了处理 DST,Rust 有一个 trait 来决定一个类型的大小是否在编译时可知,这就是 `Sized`。这个 trait 自动为编译器在编译时就知道大小的类型实现。另外,Rust 隐式的为每一个泛型函数增加了 `Sized` bound。也就是说,对于如下泛型函数定义:
|
||||
<!-- If we end up keeping the section on object safety in ch 17, we should add
|
||||
a back reference here. /Carol -->
|
||||
|
||||
```rust
|
||||
<!-- I think we dropped that one, right? -->
|
||||
<!-- We cut a large portion of it, including the part about `Sized`, so I
|
||||
didn't add a back reference. /Carol -->
|
||||
|
||||
为了处理 DST,Rust 有一个特定的 trait 来决定一个类型的大小是否在编译时可知:这就是 `Sized` trait。这个 trait 自动为编译器在编译时就知道大小的类型实现。另外,Rust 隐式的为每一个泛型函数增加了 `Sized` bound。也就是说,对于如下泛型函数定义:
|
||||
|
||||
```rust,ignore
|
||||
fn generic<T>(t: T) {
|
||||
// --snip--
|
||||
}
|
||||
```
|
||||
|
||||
实际上被当作如下处理:
|
||||
|
||||
```rust
|
||||
```rust,ignore
|
||||
fn generic<T: Sized>(t: T) {
|
||||
// --snip--
|
||||
}
|
||||
```
|
||||
|
||||
泛型函数默认只能用于在编译时已知大小的类型。然而可以使用如下特殊语法来放松这个限制:
|
||||
泛型函数默认只能用于在编译时已知大小的类型。然而可以使用如下特殊语法来放宽这个限制:
|
||||
|
||||
```rust
|
||||
```rust,ignore
|
||||
fn generic<T: ?Sized>(t: &T) {
|
||||
// --snip--
|
||||
}
|
||||
```
|
||||
|
||||
`?Sized` trait bound 与 `Sized` 相对;也就是说,它可以读作“`T` 可能是也可能不是 `Sized` 的”。这个语法只能用于 `Sized` ,而不是其他 trait。
|
||||
`?Sized` trait bound 与 `Sized` 相对;也就是说,它可以读作 “`T` 可能是也可能不是 `Sized` 的”。这个语法只能用于 `Sized` ,而不能用于其他 trait。
|
||||
|
||||
另外注意我们将 `t` 参数的类型从 `T` 变为了 `&T`:因为其类型可能不是 `Sized` 的,所以需要将其置于某种指针之后。在这个例子中选择了引用。
|
||||
|
||||
|
@ -2,13 +2,18 @@
|
||||
|
||||
> [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
|
||||
> commit 9d5b9a573daf5fa0c98b3a3005badcea4a0a5211
|
||||
|
||||
最后让我们讨论一些有关函数和闭包的高级功能:函数指针、发散函数和返回值闭包。
|
||||
|
||||
### 函数指针
|
||||
|
||||
我们讨论过了如何向函数传递闭包,不过也可以向函数传递常规的函数!函数的类型是 `fn`,使用小写的 “f” 以便不与 `Fn` 闭包 trait 向混淆。`fn` 被称为**函数指针**(*function pointer*)。指定参数为函数指针的语法类似于闭包,如列表 19-34 所示:
|
||||
<!-- Maybe give an example of when we'd want to use this? -->
|
||||
<!-- Added a short sentence, but we discuss interfacing with languages that
|
||||
don't have closures below, which I don't think makes sense until we define how
|
||||
function pointers are different than closures... /Carol -->
|
||||
|
||||
我们讨论过了如何向函数传递闭包;也可以向函数传递常规函数!这在我们希望传递已经定义的函数而不是重新定义闭包作为参数是很有用。通过函数指针允许我们使用函数作为另一个函数的参数。函数的类型是 `fn`,使用小写的 “f” 以便不与 `Fn` 闭包 trait 向混淆。`fn` 被称为**函数指针**(*function pointer*)。指定参数为函数指针的语法类似于闭包,如示例 19-34 所示:
|
||||
|
||||
<span class="filename">文件名: src/main.rs</span>
|
||||
|
||||
@ -28,15 +33,17 @@ fn main() {
|
||||
}
|
||||
```
|
||||
|
||||
<span class="caption">列表 19-34:使用 `fn` 类型接受函数指针作为参数</span>
|
||||
<span class="caption">示例 19-35: 使用 `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 语言的函数可以接受函数作为参数,但没有闭包。
|
||||
函数指针实现了所有三个闭包 trait(`Fn`、`FnMut` 和 `FnOnce`),所以总是可以在调用期望闭包的函数时传递函数指针作为参数。倾向于编写使用泛型和闭包 trait 的函数,这样它就能接受函数或闭包作为参数。
|
||||
|
||||
比如,如果希望使用 `map` 函数将一个数字 vector 转换为一个字符串 vector,就可以使用闭包:
|
||||
一个只期望接受 `fn` 而不接受闭包的情况的例子是与不存在闭包的外部代码交互时:C 语言的函数可以接受函数作为参数,但没有闭包。
|
||||
|
||||
作为一个既可以使用内联定义的闭包又可以使用命名函数的例子,让我们看看一个 `map` 的应用。使用 `map` 函数将一个数字 vector 转换为一个字符串 vector,就可以使用闭包:
|
||||
|
||||
```rust
|
||||
let list_of_numbers = vec![1, 2, 3];
|
||||
@ -56,17 +63,17 @@ let list_of_strings: Vec<String> = list_of_numbers
|
||||
.collect();
|
||||
```
|
||||
|
||||
注意这里必须使用“高级 trait”部分讲到的完全限定语法,因为存在多个叫做 `to_string` 的函数;这里使用定义于 `ToString` trait 的 `to_string` 函数,标准库为所有实现了 `Display` 的类型实现了这个 trait。
|
||||
注意这里必须使用 “高级 trait” 部分讲到的完全限定语法,因为存在多个叫做 `to_string` 的函数;这里使用了定义于 `ToString` trait 的 `to_string` 函数,标准库为所有实现了 `Display` 的类型实现了这个 trait。
|
||||
|
||||
一些人倾向于函数风格,一些人喜欢闭包。他们最终都会产生同样的代码,所以请使用你更明白的吧。
|
||||
一些人倾向于函数风格,一些人喜欢闭包。他们最终都会产生同样的代码,所以请使用对你来说更明白的吧。
|
||||
|
||||
### 返回闭包
|
||||
|
||||
因为闭包以 trait 的形式体现,返回闭包就有点微妙了;不能直接这么做。对于大部分需要返回 trait 的情况,可以使用是实现了期望返回的 trait 的具体类型替代函数的返回值。但是这不能用于闭包。他们没有一个可返回的具体类型;例如不允许使用函数指针 `fn` 作为返回值类型。
|
||||
闭包表现为 trait,这意味着不能直接返回闭包。对于大部分需要返回 trait 的情况,可以使用是实现了期望返回的 trait 的具体类型替代函数的返回值。但是这不能用于闭包,因为他们没有一个可返回的具体类型;例如不允许使用函数指针 `fn` 作为返回值类型。
|
||||
|
||||
这段代码尝试直接返回闭包,它并不能编译:
|
||||
|
||||
```rust
|
||||
```rust,ignore
|
||||
fn returns_closure() -> Fn(i32) -> i32 {
|
||||
|x| x + 1
|
||||
}
|
||||
@ -74,21 +81,21 @@ fn returns_closure() -> Fn(i32) -> i32 {
|
||||
|
||||
编译器给出的错误是:
|
||||
|
||||
```
|
||||
```text
|
||||
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`
|
||||
1 | fn returns_closure() -> Fn(i32) -> i32 {
|
||||
| ^^^^^^^^^^^^^^ `std::ops::Fn(i32) -> i32 + 'static`
|
||||
does not have a constant size known at compile-time
|
||||
|
|
||||
= note: `std::ops::Fn(i32) -> i32 + 'static` does not have a constant size
|
||||
known at compile-time
|
||||
= help: the trait `std::marker::Sized` is not implemented for
|
||||
`std::ops::Fn(i32) -> i32 + 'static`
|
||||
= note: the return type of a function must have a statically known size
|
||||
```
|
||||
|
||||
又是 `Sized` trait!Rust 并不知道需要多少空间来储存闭包。不过我们在上一部分见过这种情况的解决办法:可以使用 trait 对象:
|
||||
错误有一次指向了 `Sized` trait!Rust 并不知道需要多少空间来储存闭包。不过我们在上一部分见过这种情况的解决办法:可以使用 trait 对象:
|
||||
|
||||
```rust
|
||||
fn returns_closure() -> Box<Fn(i32) -> i32> {
|
||||
@ -96,10 +103,10 @@ fn returns_closure() -> Box<Fn(i32) -> i32> {
|
||||
}
|
||||
```
|
||||
|
||||
关于 trait 对象的更多内容,请参考第十八章。
|
||||
这段代码正好可以编译。关于 trait 对象的更多内容,请回顾第十七章的 “trait 对象” 部分。
|
||||
|
||||
## 总结
|
||||
|
||||
好的!现在我们学习了 Rust 并不常用但你可能用得着的功能。我们介绍了很多复杂的主题,这样当你在错误信息提示或阅读他人代码时遇到他们时,至少可以说已经见过这些概念和语法了。
|
||||
好的!现在我们学习了 Rust 并不常用但在特定情况下你可能用得着的功能。我们介绍了很多复杂的主题,这样若你在错误信息提示或阅读他人代码时遇到他们,至少可以说之前已经见过这些概念和语法了。你可以使用本章作为一个解决方案的参考。
|
||||
|
||||
现在,让我们再开始一个项目,将本书所学的所有内容付与实践!
|
@ -2,15 +2,17 @@
|
||||
|
||||
> [ch20-00-final-project-a-web-server.md](https://github.com/rust-lang/book/blob/master/second-edition/src/ch20-00-final-project-a-web-server.md)
|
||||
> <br>
|
||||
> commit 08e50d5e147ad290d88efd5c58365000723626df
|
||||
> commit e2a38b44f3a7f796fa8000e558dc8dd2ddf340a3
|
||||
|
||||
这是一次漫长的旅途,不过我们做到了!这一章便是本书的结束。离别是如此甜蜜的悲伤。不过在我们结束之前,再来一起构建另一个项目,来展示最后几章所学,同时复习更早的章节。
|
||||
|
||||
下面就是我们将要做的:一个简单的 web server:
|
||||
作为最后的项目,我们将要实现一个只返回 “hello” 的 web server;它在浏览器中看起来就如图例 20-1 所示:
|
||||
|
||||
![hello from rust](img/trpl20-01.png)
|
||||
|
||||
为此我们将:
|
||||
<span class="caption">图例 20-1: 我们最好将一起分享的项目</span>
|
||||
|
||||
如下是我们将怎样构建此 web server 的计划:
|
||||
|
||||
1. 学习一些 TCP 与 HTTP 知识
|
||||
2. 在套接字(socket)上监听 TCP 请求
|
||||
@ -18,6 +20,6 @@
|
||||
4. 创建一个合适的 HTTP 响应
|
||||
5. 通过线程池改善 server 的吞吐量
|
||||
|
||||
在开始之前,需要提到一点:如果你曾在生产环境中编写过这样的代码,还有很多更好的做法。特别需要指出的是,crates.io 上提供了很多更完整健壮的 web server 和 线程池实现,要比我们编写的好很多。
|
||||
不过在开始之前,需要提到一点:这里使用的方法并不是使用 Rust 构建 web server 最好的方法。*https://crates.io* 上有很多可用于生产环境的 crate,它们提供了比我们所要编写的更为完整的 web server 和线程池实现。
|
||||
|
||||
然而,本章的目的在于学习,而不是走捷径。因为 Rust 是一个系统编程语言,能够选择处理什么层次的抽象。我们能够选择比其他语言可能或可用的层次更低的层次。所以我们将自己编写一个基础的 HTTP server 和线程池,以便学习将来可能用到的 crate 背后的通用理念和技术。
|
||||
然而,本章的目的在于学习,而不是走捷径。因为 Rust 是一个系统编程语言,我们能够选择处理什么层次的抽象,并能够选择比其他语言可能或可用的层次更低的层次。因此我们将自己编写一个基础的 HTTP server 和线程池,以便学习将来可能用到的 crate 背后的通用理念和技术。
|
||||
|
Loading…
Reference in New Issue
Block a user