mirror of
https://github.com/rust-lang-cn/book-cn.git
synced 2025-02-02 15:28:40 +08:00
Update content
This commit is contained in:
parent
a81ae58322
commit
3b600f02df
@ -97,7 +97,7 @@ let loopback = IpAddr::V6(String::from("::1"));
|
||||
|
||||
我们直接将数据附加到枚举的每个成员上,这样就不需要一个额外的结构体了。
|
||||
|
||||
用枚举替代结构体还有另一个优势:每个成员可以处理不同类型和数量的数据。IPv4 版本的 IP 地址总是含有四个值在 0 和 255 之间的数字部分。如果我们想要将 `V4` 地址存储为四个 `u8` 值而 `V6` 地址仍然表现为一个 `String`,这就不能使用结构体了。枚举则可以轻易的处理这个情况:
|
||||
用枚举替代结构体还有另一个优势:每个成员可以处理不同类型和数量的数据。IPv4 版本的 IP 地址总是含有四个值在 0 和 255 之间的数字部分。如果我们想要将 `V4` 地址存储为四个 `u8` 值而 `V6` 地址仍然表现为一个 `String`,这就不能使用结构体了。枚举则可以轻易地处理这个情况:
|
||||
|
||||
```rust
|
||||
enum IpAddr {
|
||||
@ -256,7 +256,7 @@ not satisfied
|
||||
|
||||
换句话说,在对 `Option<T>` 进行 `T` 的运算之前必须将其转换为 `T`。通常这能帮助我们捕获到空值最常见的问题之一:假设某值不为空但实际上为空的情况。
|
||||
|
||||
不再担心会错误的假设一个非空值,会让你对代码更加有信心。为了拥有一个可能为空的值,你必须要显式的将其放入对应类型的 `Option<T>` 中。接着,当使用这个值时,必须明确的处理值为空的情况。只要一个值不是 `Option<T>` 类型,你就 **可以** 安全的认定它的值不为空。这是 Rust 的一个经过深思熟虑的设计决策,来限制空值的泛滥以增加 Rust 代码的安全性。
|
||||
不再担心会错误的假设一个非空值,会让你对代码更加有信心。为了拥有一个可能为空的值,你必须要显式的将其放入对应类型的 `Option<T>` 中。接着,当使用这个值时,必须明确地处理值为空的情况。只要一个值不是 `Option<T>` 类型,你就 **可以** 安全的认定它的值不为空。这是 Rust 的一个经过深思熟虑的设计决策,来限制空值的泛滥以增加 Rust 代码的安全性。
|
||||
|
||||
那么当有一个 `Option<T>` 的值时,如何从 `Some` 成员中取出 `T` 的值来使用它呢?`Option<T>` 枚举拥有大量用于各种情况的方法:你可以查看[它的文档][docs]<!-- ignore -->。熟悉 `Option<T>` 的方法将对你的 Rust 之旅非常有用。
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
## 包和 crate
|
||||
|
||||
模块系统的第一部分,我们将介绍包和 crate。crate 是一个二进制项或者库。*crate root* 是一个源文件,Rust 编译器以它为起始点,并构成你的 crate 的根模块(我们将在 “[定义模块来控制作用域与私有性](https://github.com/rust-lang/book/blob/master/src/ch07-02-defining-modules-to-control-scope-and-privacy.md)” 一节深入解读)。*包*(*package*) 是提供一系列功能的一个或者多个 crate。一个包会包含有一个 *Cargo.toml* 文件,阐述如何去构建这些 crate。
|
||||
模块系统的第一部分,我们将介绍包和 crate。crate 是一个二进制项或者库。*crate root* 是一个源文件,Rust 编译器以它为起始点,并构成你的 crate 的根模块(我们将在[“定义模块来控制作用域与私有性”][modules]<!-- ignore -->一节深入解读)。*包*(*package*) 是提供一系列功能的一个或者多个 crate。一个包会包含有一个 *Cargo.toml* 文件,阐述如何去构建这些 crate。
|
||||
|
||||
包中所包含的内容由几条规则来确立。一个包中至多 **只能** 包含一个库 crate(library crate);包中可以包含任意多个二进制 crate(binary crate);包中至少包含一个 crate,无论是库的还是二进制的。
|
||||
|
||||
@ -20,8 +20,11 @@ main.rs
|
||||
|
||||
在此,我们有了一个只包含 *src/main.rs* 的包,意味着它只含有一个名为 `my-project` 的二进制 crate。如果一个包同时含有 *src/main.rs* 和 *src/lib.rs*,则它有两个 crate:一个库和一个二进制项,且名字都与包相同。通过将文件放在 *src/bin* 目录下,一个包可以拥有多个二进制 crate:每个 *src/bin* 下的文件都会被编译成一个独立的二进制 crate。
|
||||
|
||||
一个 crate 会将一个作用域内的相关功能分组到一起,使得该功能可以很方便地在多个项目之间共享。举一个例子,我们在 [第 2 章](https://github.com/rust-lang/book/blob/master/src/ch02-00-guessing-game-tutorial.md#generating-a-random-number) 使用的 `rand` crate 提供了生成随机数的功能。通过将 `rand` crate 加入到我们项目的作用域中,我们就可以在自己的项目中使用该功能。`rand` crate 提供的所有功能都可以通过该 crate 的名字:`rand` 进行访问。
|
||||
一个 crate 会将一个作用域内的相关功能分组到一起,使得该功能可以很方便地在多个项目之间共享。举一个例子,我们在[第 2 章][rand]<!-- ignore -->使用的 `rand` crate 提供了生成随机数的功能。通过将 `rand` crate 加入到我们项目的作用域中,我们就可以在自己的项目中使用该功能。`rand` crate 提供的所有功能都可以通过该 crate 的名字:`rand` 进行访问。
|
||||
|
||||
将一个 crate 的功能保持在其自身的作用域中,可以知晓一些特定的功能是在我们的 crate 中定义的还是在 `rand` crate 中定义的,这可以防止潜在的冲突。例如,`rand` crate 提供了一个名为 `Rng` 的特性(trait)。我们还可以在我们自己的 crate 中定义一个名为 `Rng` 的 `struct`。因为一个 crate 的功能是在自身的作用域进行命名的,当我们将 `rand` 作为一个依赖,编译器不会混淆 `Rng` 这个名字的指向。在我们的 crate 中,它指向的是我们自己定义的 `struct Rng`。我们可以通过 `rand::Rng` 这一方式来访问 `rand` crate 中的 `Rng` 特性(trait)。
|
||||
|
||||
接下来让我们来说一说模块系统!
|
||||
|
||||
[modules]: ch07-02-defining-modules-to-control-scope-and-privacy.html
|
||||
[rand]: ch02-00-guessing-game-tutorial.html#生成一个随机数
|
||||
|
@ -4,7 +4,7 @@
|
||||
|
||||
尽管代码可能没问题,但如果 Rust 编译器没有足够的信息可以确定,它将拒绝代码。
|
||||
|
||||
不安全 Rust 之所以存在,是因为静态分析本质上是保守的。当编译器尝试确定一段代码是否支持某个保证时,拒绝一些有效的程序比接受无效程序要好一些。这必然意味着有时代码**可能**是合法的,但如果 Rust 编译器没有足够的信息来确定,它将拒绝该代码。在这种情况下,可以使用不安全代码告诉编译器,“相信我,我知道我在干什么。”这么做的缺点就是你只能靠自己了:如果不安全代码出错了,比如解引用空指针,可能会导致不安全的内存使用。
|
||||
不安全 Rust 之所以存在,是因为静态分析本质上是保守的。当编译器尝试确定一段代码是否支持某个保证时,它最好拒绝一些有效的程序而不是接受一些无效的程序。这必然意味着有时代码**可能**是合法的,但如果 Rust 编译器没有足够的信息来确定,它将拒绝该代码。在这种情况下,可以使用不安全代码告诉编译器,“相信我,我知道我在干什么。”这么做的缺点就是你只能靠自己了:如果不安全代码出错了,比如解引用空指针,可能会导致不安全的内存使用。
|
||||
|
||||
另一个 Rust 存在不安全一面的原因是:底层计算机硬件固有的不安全性。如果 Rust 不允许进行不安全操作,那么有些任务则根本完成不了。Rust 需要能够进行像直接与操作系统交互,甚至于编写你自己的操作系统这样的底层系统编程!这也是 Rust 语言的目标之一。让我们看看不安全 Rust 能做什么,和怎么做。
|
||||
|
||||
@ -32,7 +32,7 @@
|
||||
|
||||
回到第 4 章的 [“悬垂引用”][dangling-references]<!-- ignore --> 部分,那里提到了编译器会确保引用总是有效的。不安全 Rust 有两个被称为 **裸指针**(*raw pointers*)的类似于引用的新类型。和引用一样,裸指针是不可变或可变的,分别写作 `*const T` 和 `*mut T`。这里的星号不是解引用运算符;它是类型名称的一部分。在裸指针的上下文中,**不可变** 意味着指针解引用之后不能直接赋值。
|
||||
|
||||
与引用和智能指针的区别在于,记住裸指针
|
||||
裸指针与引用和智能指针的区别在于:
|
||||
|
||||
* 允许忽略借用规则,可以同时拥有不可变和可变的指针,或多个指向相同位置的可变指针
|
||||
* 不保证指向有效的内存
|
||||
@ -83,7 +83,7 @@ unsafe {
|
||||
|
||||
创建一个指针不会造成任何危险;只有当访问其指向的值时才有可能遇到无效的值。
|
||||
|
||||
还需注意示例 19-1 和 19-3 中创建了同时指向相同内存位置 `num` 的裸指针 `*const i32` 和 `*mut i32`。相反如果尝试创建 `num` 的不可变和可变引用,这将无法编译因为 Rust 的所有权规则不允许拥有可变引用的同时拥有不可变引用。通过裸指针,就能够同时创建同一地址的可变指针和不可变指针,若通过可变指针修改数据,则可能潜在造成数据竞争。请多加小心!
|
||||
还需注意示例 19-1 和 19-3 中创建了同时指向相同内存位置 `num` 的裸指针 `*const i32` 和 `*mut i32`。相反如果尝试同时创建 `num` 的不可变和可变引用,将无法通过编译,因为 Rust 的所有权规则不允许在拥有任何不可变引用的同时再创建一个可变引用。通过裸指针,就能够同时创建同一地址的可变指针和不可变指针,若通过可变指针修改数据,则可能潜在造成数据竞争。请多加小心!
|
||||
|
||||
既然存在这么多的危险,为何还要使用裸指针呢?一个主要的应用场景便是调用 C 代码接口,这在下一部分 [“调用不安全函数或方法”](#调用不安全函数或方法) 中会讲到。另一个场景是构建借用检查器无法理解的安全抽象。让我们先介绍不安全函数,接着看一看使用不安全代码的安全抽象的例子。
|
||||
|
||||
|
@ -48,7 +48,7 @@ pub trait Iterator<T> {
|
||||
|
||||
区别在于当如示例 19-13 那样使用泛型时,则不得不在每一个实现中标注类型。这是因为我们也可以实现为 `Iterator<String> for Counter`,或任何其他类型,这样就可以有多个 `Counter` 的 `Iterator` 的实现。换句话说,当 trait 有泛型参数时,可以多次实现这个 trait,每次需改变泛型参数的具体类型。接着当使用 `Counter` 的 `next` 方法时,必须提供类型注解来表明希望使用 `Iterator` 的哪一个实现。
|
||||
|
||||
通过关联类型,则无需标注类型因为不能多次实现这个 trait。对于示例 19-12 使用关联类型的定义,我们只能选择一次 `Item` 会是什么类型,因为只能有一个 `impl Iterator for Counter`。当调用 `Counter` 的 `next` 时不必每次指定我们需要 `u32` 值的迭代器。
|
||||
通过关联类型,则无需标注类型,因为不能多次实现这个 trait。对于示例 19-12 使用关联类型的定义,我们只能选择一次 `Item` 会是什么类型,因为只能有一个 `impl Iterator for Counter`。当调用 `Counter` 的 `next` 时不必每次指定我们需要 `u32` 值的迭代器。
|
||||
|
||||
### 默认泛型类型参数和运算符重载
|
||||
|
||||
|
@ -185,7 +185,7 @@ loop {
|
||||
|
||||
因为 Rust 需要知道例如应该为特定类型的值分配多少空间这样的信息其类型系统的一个特定的角落可能令人迷惑:这就是 **动态大小类型**(*dynamically sized types*)的概念。这有时被称为 “DST” 或 “unsized types”,这些类型允许我们处理只有在运行时才知道大小的类型。
|
||||
|
||||
让我们深入研究一个贯穿本书都在使用的动态大小类型的细节:`str`。没错,不是 `&str`,而是 `str` 本身。`str` 是一个 DST;直到运行时我们都不知道字符串有多长。因为直到运行时都不能知道大其小,也就意味着不能创建 `str` 类型的变量,也不能获取 `str` 类型的参数。考虑一下这些代码,他们不能工作:
|
||||
让我们深入研究一个贯穿本书都在使用的动态大小类型的细节:`str`。没错,不是 `&str`,而是 `str` 本身。`str` 是一个 DST;直到运行时我们都不知道字符串有多长。因为直到运行时都不能知道其大小,也就意味着不能创建 `str` 类型的变量,也不能获取 `str` 类型的参数。考虑一下这些代码,他们不能工作:
|
||||
|
||||
```rust,ignore,does_not_compile
|
||||
let s1: str = "Hello there!";
|
||||
@ -194,7 +194,7 @@ let s2: str = "How's it going?";
|
||||
|
||||
Rust 需要知道应该为特定类型的值分配多少内存,同时所有同一类型的值必须使用相同数量的内存。如果允许编写这样的代码,也就意味着这两个 `str` 需要占用完全相同大小的空间,不过它们有着不同的长度。这也就是为什么不可能创建一个存放动态大小类型的变量的原因。
|
||||
|
||||
那么该怎么办呢?你已经知道了这种问题的答案:`s1` 和 `s2` 的类型是 `&str` 而不是 `str`。如果你回想第 4 章 [“切片 slice 类型”][string-slices]<!-- ignore --> 部分,slice 数据结储存了开始位置和 slice 的长度。
|
||||
那么该怎么办呢?你已经知道了这种问题的答案:`s1` 和 `s2` 的类型是 `&str` 而不是 `str`。如果你回想第 4 章 [“切片 slice 类型”][string-slices]<!-- ignore --> 部分,slice 数据结构储存了开始位置和 slice 的长度。
|
||||
|
||||
所以虽然 `&T` 是一个储存了 `T` 所在的内存位置的单个值,`&str` 则是 **两个** 值:`str` 的地址和其长度。这样,`&str` 就有了一个在编译时可以知道的大小:它是 `usize` 长度的两倍。也就是说,我们总是知道 `&str` 的大小,而无论其引用的字符串是多长。这里是 Rust 中动态大小类型的常规用法:他们有一些额外的元信息来储存动态信息的大小。这引出了动态大小类型的黄金规则:必须将动态大小类型的值置于某种指针之后。
|
||||
|
||||
|
@ -16,7 +16,7 @@
|
||||
|
||||
一个函数标签必须声明函数参数个数和类型。相比之下,宏能够接受不同数量的参数:用一个参数调用 `println!("hello")` 或用两个参数调用 `println!("hello {}", name)` 。而且,宏可以在编译器翻译代码前展开,例如,宏可以在一个给定类型上实现 trait 。而函数则不行,因为函数是在运行时被调用,同时 trait 需要在编译时实现。
|
||||
|
||||
实现一个宏而不是函数的消极面是宏定义要比函数定义更复杂,因为你正在编写生成 Rust 代码的 Rust 代码。由于这样的间接性,宏定义通常要比函数定义更难阅读、理解以及维护。
|
||||
实现一个宏而不是一个函数的缺点是宏定义要比函数定义更复杂,因为你正在编写生成 Rust 代码的 Rust 代码。由于这样的间接性,宏定义通常要比函数定义更难阅读、理解以及维护。
|
||||
|
||||
宏和函数的最后一个重要的区别是:在一个文件里调用宏 **之前** 必须定义它,或将其引入作用域,而函数则可以在任何地方定义和调用。
|
||||
|
||||
@ -94,7 +94,7 @@ temp_vec
|
||||
|
||||
有三种类型的过程宏(自定义派生(derive),类属性和类函数),不过它们的工作方式都类似。
|
||||
|
||||
当创建过程宏时,其定义必须位于一种特殊类型的属于它们自己的 crate 中。这么做出于复杂的技术原因,将来我们希望能够消除这些限制。使用这些宏需采用类似示例 19-29 所示的代码形式,其中 `some_attribute` 是一个使用特定宏的占位符。
|
||||
创建过程宏时,其定义必须驻留在它们自己的具有特殊 crate 类型的 crate 中。这么做出于复杂的技术原因,将来我们希望能够消除这些限制。使用这些宏需采用类似示例 19-29 所示的代码形式,其中 `some_attribute` 是一个使用特定宏的占位符。
|
||||
|
||||
<span class="filename">文件名: src/lib.rs</span>
|
||||
|
||||
@ -108,9 +108,9 @@ pub fn some_name(input: TokenStream) -> TokenStream {
|
||||
|
||||
<span class="caption">示例 19-29: 一个使用过程宏的例子</span>
|
||||
|
||||
过程宏包含一个函数,这也是其得名的原因:“过程” 是 “函数” 的同义词。那么为何不叫 “函数宏” 呢?好吧,有一个过程宏是 “类函数” 的,叫成函数会产生混乱。无论如何,定义过程宏的函数接受一个 `TokenStream` 作为输入并产生一个 `TokenStream` 作为输出。这也就是宏的核心:宏所处理的源代码组成了输入 `TokenStream`,同时宏生成的代码是输出 `TokenStream`。最后,函数上有一个属性;这个属性表明过程宏的类型。在同一 crate 中可以有多种的过程宏。
|
||||
定义过程宏的函数以一个 `TokenStream` 作为输入并产生一个 `TokenStream` 作为输出。该 `TokenStream` 类型由包含在 Rust 中的 `proc_macroRust` crate 定义,并表示令牌序列。这是宏的核心:宏操作的源代码构成了输入 `TokenStream`,宏产生的代码是输出 `TokenStream`。该函数还附加了一个属性,该属性指定我们正在创建过程宏的类型。我们可以在同一个 crate 中拥有多种过程宏。
|
||||
|
||||
考虑到这些宏是如此类似,我们会从自定义派生宏开始。接着会解释与其他形式宏的微小区别。
|
||||
让我们看看不同种类的过程宏。我们将从自定义派生宏开始,然后解释与其他形式宏不同的细微差别。
|
||||
|
||||
### 如何编写自定义 `derive` 宏
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user