rust-lang-zh_CN/src/Ch21_Appendix.md
2023-04-21 13:11:25 +08:00

46 KiB
Raw Blame History

附录

以下小节包含了在咱们的 Rust 路途中,会发现有用的一些参考资料。

附录 A关键字

以下清单包含了 Rust 语言当前或今后要用到的一些关键字。由此,他们便不能被用作标识符(除在 “原始标识符” 小节中咱们将讨论的那些外)了。所谓标识符,是函数、变量、参数、结构体字段、模组、代码箱、常量、宏、静态值、属性、类型、特质或生命周期等的名字。

当前在用的关键字

下面是当前在用关键字的清单,带有其作用描述。

  • as - 执行原生强制转换primitive casting消除包含着某个项目的特定特质歧义disambiguate the specific trait containing a item或重命名 use 语句中的项目;
  • async - 返回一个 Future 类型值,而非阻塞当前线程;
  • await - 在某个 Future 值的结果准备好前,暂停程序执行;
  • break - 立即退出某个循环;
  • const - 定义出常量项目或常量原始指针;
  • continue - 继续下一循环迭代;
  • crate - 在模组路径中,指向代码箱根;
  • dyn - 动态调遣到某个特质对象,参考 特质对象执行动态调遣;
  • else - if 的回退,及 if let 控制流的构件;
  • extern - 链接外部函数或变量;
  • false - 布尔值假的字面值;
  • fn - 定义出某个函数或函数指针类型;
  • for - 对某个迭代器的项目加以迭代、实现某个特质或指明某个更高级别的生命周期a higher-ranked lifetime;
  • if - 基于某个条件表达式结果的分支;
  • impl - 实现固有或特质功能implement inherent or trait functionality;
  • in - for 循环语法的一部分;
  • let - 绑定某个变量;
  • loop - 无条件地循环;
  • match - 将某个值与模式匹配;
  • mod - 定义出模组;
  • move - 领导闭包取得其所有捕获值的所有权;
  • mut - 注解出引用、原始指针或模式绑定等中的可变性;
  • pub - 注解出结构体、impl 代码块或模组等中的公开可见性;
  • ref - 按引用绑定;
  • return - 自函数返回值;
  • Self - 咱们正定义或实现中类型的类型别名;
  • self - 方法主体method subject或当前模组
  • static - 在整个程序执行过程持续有效的全局变量或生命周期;
  • struct - 定义出某个结构体;
  • super - 当前模组的父模组;
  • trait - 定义出某个特质;
  • true - 布尔值真的字面值;
  • type - 定义出某个类型别名或关联类型;
  • union - 定义出某个 联合体,是在联合体声明时用到的唯一关键字;
  • unsafe - 注解非安全代码、函数、特质或一些实现;
  • use - 将符号带入到作用域;
  • where - 注解约束某个类型的子句;
  • while - 基于某个表达式结果而有条件的循环。

为今后使用保留的关键字

以下关键字尚无任何功能,但被 Rust 为今后的潜在使用而保留。

  • abstract
  • become
  • box
  • do
  • final
  • macro
  • override
  • priv
  • try
  • typeof
  • unsized
  • virtual
  • yield

原始标识符

原始标识符raw identifiers 属于允许实现使用一般不被允许关键字的语法。是通过在关键字前加上前缀 r#,使用原始标识符的。

比如,match 是个关键字。在咱们尝试编译下面这个使用 match 作其名字的函数时:

文件名:src/main.rs

fn match(needle: &str, haystack: &str) -> bool {
    haystack.contains(needle)
}

咱们将得到这样的报错:

error: expected identifier, found keyword `match`
 --> src/main.rs:1:4
  |
1 | fn match(needle: &str, haystack: &str) -> bool {
  |    ^^^^^ expected identifier, found keyword

该报错显示咱们无法将关键字 match 用作函数标识符。要将 match 用作函数名字,咱们就需要使用原始标识符语法,像下面这样:

文件名:src/main.rs

fn r#match(needle: &str, haystack: &str) -> bool {
    haystack.contains(needle)
}

fn main() {
    assert! (r#match("foo", "foobar"));
}

此代码将不带任何错误地编译。请注意那个函数的定义中,与 main 中该函数被调用处其名字上的 r# 前缀。

原始标识符实现了将任何咱们所选的词语用作标识符,即使那个词语碰巧是个保留的关键字。这给到咱们更自由地选择标识符名字,以及实现与一些以其中这些词语不属于关键字的语言,所编写的程序集成。此外,原始标识符实现了,对那些以不同于咱们代码箱 Rust 版本编写库加以运用。比如,在 2015 版中 try 就不是个关键字,但在 2018 版本中却是。若咱们依赖于一个使用 2015 版本编写的库,而该库有一个 try 函数,那么咱们就将需要在这种情况下,使用原始标识符 r#try,来从咱们的 2018 版本的代码,调用那个函数。请参阅 附录 E 了解更多有关版本的信息。

附录 B运算符与符号

此附录包含了 Rust 语法的词汇表,包括运算符及别的一些,自己单独出现或出现于路径、泛型、特质边界、宏、属性、注释、元组及方括符等上下文中的符号。

运算符

表 B-1 包含了 Rust 中的符号、该符号将如何出现于上下文中的一个示例、简单的解释,以及该运算符是否可过载。若某个运算符可以过载,就会列出过载那个运算符要用到的相关特质。

表 B-1运算符

运算符 示例 说明 是否可以过载
! ident! (...)
ident! {...}
ident! [...]
宏扩展
! !expr 按位或逻辑求补运算
!= expr != expr 不等比较 PartialEq
% expr % expr 算术求余运算 Rem
%= var %= expr 算术求余并赋值 RemAssign
& &expr, &mut expr 借用
& &type, &mut type, &'a type, &'a mut type 借用指针类型
& expr & expr 按位与AND运算 BitAnd
&= var &= expr 按位与AND运算并赋值 BitAndAssign
&& expr && expr 短路逻辑与AND运算short-circuit logical AND
* expr * expr 算术乘法运算 Mul
*= var *= expr 算术乘法运算并赋值 MulAssign
* *expr 解引用运算 Deref
* *const type, *mut type 原始指针运算
+ trait + trait, 'a + trait 复合类型约束运算
+ expr + expr 算术加法运算 Add
+= var += expr 算术加法运算并赋值 AddAssign
, expr, expr 参数与元素分隔符
- - expr 算术取反运算 Neg
- expr - expr 算术减法运算 Sub
-= var -= expr 算术减法运算并赋值 SubAssign
-> fn(...) -> type, |...| -> type 函数与闭包的返回值类型
. expr.ident 成员访问
.. .., expr.., ..expr, expr..expr 排除右侧的范围语法字面值 PartialOrd
..= ..=expr, expr..=expr 包含右侧范围语法字面值 PartialOrd
.. ..expr 结构体更新语法
.. variant(x, ..), struct_type { x, .. } “等等” 模式绑定,"And the rest" pattern binding
... expr...expr (已弃用,请使用 ..= 代替)在模式中:包含式范围模式
/ expr / expr 算术除法运算 Div
/= var /= expr 算术除法并赋值 DivAssign
: pat: type, ident: type 约束
: ident: expr 结构体字段初始化
: 'a: loop {...} 循环标签
; expr; 语句及项目的终止符
; [..., len] 固定大小数组语法的一部分
<< expr << expr 向左移位运算 Shl
<<= var <<= expr 向左移位运算并赋值 ShlAssign
< expr < expr 小于比较 PartialOrd
<= expr <= expr 小于等于比较 PartialOrd
= var = expr, ident = type 赋值/等价equivalence
== expr == expr 相等比较 PartialEq
=> pat => expr 匹配支臂语法的一部分
> expr > expr 大于比较 PartialOrd
>= expr >= expr 大于等于比较 PartialOrd
>> expr >> expr 向右位移运算 Shr
>>= var >>= expr 向右位移运算并赋值 ShrAssign
@ ident @ pat 模式绑定
^ var ^ expr 按位异或运算 BitXor
^= var ^= expr 按位异或运算并赋值 BitXorAssign
| pat | pat 模式选择pattern alternatives
| expr | expr 按位或OR运算 BitOr
|= var |= expr 按位或OR运算并赋值 BitOrAssign
|| expr || expr 短路逻辑或运算Short-circuiting logical OR
? expr? 错误传递

非运算符的符号

Non-operator Symbols

以下清单包含了不以运算符发挥作用的全部符号;那就是说,他们不会表现得像函数或方法调用。

表 B-2 给出了自己单独出现,并在多种场合有效的一些符号。

表 B-2独立语法Stand-Alone Syntax

符号 说明
'ident 命名的生命周期或循环标签
...u8, ...i32, ...f64, ...usize 等等 指定类型的数字字面值
"..." 字符串字面值
r"...", r#"..."#, r##"..."## 等等 原始字符串字面值,其中的转义字符不会被处理
b"..." 字节字符串字面值;构造出一个字节数组而非字符串
br"...", br#"...", br##"..."## 等等 原始字节字符串字面值,是原始与字节字符串字面值的结合
'...' 字符字面值
b'...' ASCII 字节字面值
|...| expr 闭包
! 发散函数下总是空的底部类型always empty bottom type for diverging functions
_ “忽略ignored” 模式绑定还用于令到整数字面值可读also used to make integer literals readable

表 B-3 展示了出现在模组层次结构中到某个项目路径上下文中的一些符号。

表 B-3路径相关的语法

符号 说明
ident::ident 命名空间路径
::path 相对于代码箱根的路径(比如,某个显式绝对路径)
self::path 相对于当前模组的路径(比如,某个显式相对路径)
super::path 相对于当前模组父模组的路径
type::ident, <type as trait>::ident 关联的常量、函数及类型
<type>::... 无法直接命名的某个类型的关联项目(比如,<&T>::..., <[T]>::... 等等)
trait::method(...) 通过命名出定义方法的类型,消除该方法调用的歧义
<type as trait>::method(...) 通过命名出特质与类型,消除方法调用的歧义

表 B-4 展示了出现在运用泛型参数上下文中的一些符号。

表 B-4泛型

| 符号 | 说明 | | --- | --- | | path<...> | 指明类型中的泛型参数(比如,Vec<u8> | | path::<...>, method::<...> | 指明表达式中泛型、函数或方法的参数通常这被称作涡轮鱼语法turbofish比如"42".parse::<i32>(),关于 Rust 的 turbofish 语法,请参考:What is Rust's turbofishRUST 中的 turbofish 语法(一) ... | | fn ident<...> ... | 定义出泛型函数 | | struct ident<...> ... | 定义出泛型结构体 | | enum ident<...> ... | 定义出泛型枚举 | | impl<...> ... | 定义出泛型实现 | | for<...> type | 高阶声明周期边界higher-ranked lifetime bounds | | type<ident=type> | 其中一个或更多的关联类型有着指定赋值的某种泛型a generic type where one or more associated types have specific assignments比如Iterator<Item=T>

下表 B-5 展示了出现在使用特质边界的约束性泛型参数上下文中的一些符号table B-5 shows symbols that appear in the context of constraining generic type parameters with trait bounds。

B-5特质边界约束Trait Bound Constrains

符号 说明
T: U 泛型参数 T 受实现了 U 的类型约束
T: 'a 泛型 T 必须要比生命周期 'a 活得更久generic type T must outlive lifetime 'a(意思是该类型不能间接地包含任何生命周期短于 'a 的引用)
T: 'static 泛型 T 不包含除 'static 的引用外的其他引用
'b: 'a 泛型生命周期 'b 必须要比 'a 存活得更久
T: ?Sized 允许泛型参数为动态大小类型
'a + trait, trait + trait 复合的类型约束

下表 B-6 展示了出现在宏调用或定义上下文中,并指明了某个项目上属性的一些符号。

B-6宏与属性

符号 说明
#[meta] 外层属性
#![meta] 内层熟悉
$ident 宏代换macro substitution
$ident:kind 宏捕获
$(...) ... 宏重复macro repetition
ident! (...), ident! {...}, ident! [...] 宏调用macro invocation

下表 B-7 展示了创建注释的一些符号。

表 B-7注释

符号 说明
// 注释行
//! 内层行文档注释inner line doc comment
/// 外层行文档注释outter line doc comment
/*...*/ 注释块
/*!...*/ 内层块文档注释inner block doc comment
/**...*/ 外层块文档注释outter block doc comment

下表 B-8 展示了出现于用到元组上下文中的一些符号。

元组

符号 说明
() 空元组(又叫单元值),同时属于字面值与类型
(expr) 元括号括起来的表达式parenthesized expression
(expr,) 单一元素的元组表达式
(type,) 单一元素的元组类型single-element tuple type
(expr, ...) 元组表达式
(type, ...) 元组类型tuple type
expr(expr, ...) 函数调用表达式;还用于初始化一些元组的 struct 以及元组的 enum 变种function call expression; also used to initialize tuple structs and tuple enum vairants
expr.0, expr.1 等等 对元组进行索引

下表 B-9 展示了其中用到花括号上下文中的一些符号。

表 B-9花括号

符号 说明
{...} 代码块表达式
Type {...} struct 的字面值

下表 B-10 展示了其中用到方括号上下文中的一些符号。

表 B-10方括号

符号 说明
[...] 数组的字面值
[expr; len] 包含着 exprlen 拷贝数组的字面值
[type; len] 包含着 lentype 的实例数组的字面值
expr[expr] 对集合进行索引collection indexing。是可过载的 (Index, IndexMut)overloadable (Index, IndexMut)
expr[..], expr[a..], expr[..b], expr[a..b] 用到了 RangeRangeFromRangeToRangeFull 作为 “索引”的带有集合切片集合索引collection indexing pretending to be collection slicing, using Range, RangeFrom, RangeTo, or RangeFull as the "index"

附录 C派生特质

Appendix C: Derivable Traits

本书的多个不同地方,咱们都曾讨论过 derive 属性,咱们可将其应用到结构体或枚举定义。derive 属性会在咱们以 derive 语法注解的类型上,生成将以某个特质自身默认实现,而实现该特质的代码。

在这个附录中,咱们会提供到标准库中,咱们可以与 derive 一起使用的全部特质的参考。以下各个小节均会讲到:

  • 此特质将启用那些操作符与方法;
  • derive 所提供到的该特质实现会做些什么;
  • 实现该特质对那个类型意味着什么;
  • 允许及不允许实现该特质的情况;
  • 需要该特质操作的示例。

若咱们想要不同于由 derive 属性所提供的行为,请参考 标准库文档,了解如何亲自实现各个特质的详细信息。

这里列出的这些特质,只是一些由标准库所提供的,可使用 derive 实现于咱们类型上的那些。定义在标准库中别的一些特质,则没有什么合理的默认行为,因此是否要以对于咱们正尝试完成的东西有意义的方式,实现他们就取决于咱们自己了。

不能派生的一个特质示例便是 Display其为终端用户处理格式化。咱们应始终要考虑将某个类型显示给用户的恰当方式。终端用户应被允许看到该类型的哪些部分他们会发现哪些部分是相关的数据的何种形式才是与他们最为密切相关的Rust 编译器并无这种见解,因此他就无法为咱们提供到恰当的默认行为。

这个附录中所提供到的派生特质清单并不详尽:库可以为他们自己的特质实现 derive,从而领导咱们可使用 derive 的特质清单为真正开放的。实现 derive 设计到使用程序性宏,这在第 19 章的 “关于宏” 小节讲到过。

输出给编程者的 Debug

Debug for Programmer Output

Debug 特质实现了格式字符串中的格式化,所谓格式字符串,即咱们通过在 {} 里添加 :? 所表示的。

Debug 特质允许咱们为调试目的打印某种类型的实例,如此咱们以及用到咱们类型的其他编程者,就可以在程序执行的某个特定时刻,就其某个实例加以探查。

在比如用到 assert_eq! 宏中等情况下,Debug 特质便是要求使用的。assert_eq! 这个宏在相等断言失败时,就会打印出作为参数所给到的两个实例值,如此编程者就可以看到为何这两个实例不相等。

用于相等比较的 PartialEqEq

PartialEq 特质允许咱们比较某种类型的两个实例,来检查他们是否相等,并实现 ==!= 运算符的应用。

PartialEq 进行派生,就会实现 eq 方法。当 ParitalEq 实在结构体上实现的时,只有在两个实例的 全部 字段都相等时,他们才是相等的,且在有任何字段不等时,两个实例便不相等。当在枚举上派生时,枚举的各个变种与自身相等,而不等于其他任何变种。

在使用需要能够比较某个类型的两个实例是否相等的 assert_eq! 宏时,就需要这个 PartialEq 特质。

Eq 特质则没有方法。他的目的是要表明,所注解的类型的每个值,其值都等于他自身。尽管并非所有实现 PartialEq 的类型都可以实现 Eq,但 Eq 特质却只可应用到那些同时实现了 PartialEq 的类型。这方面的一个示例便是浮点数类型浮点数的实现就表明两个非数字the not-a-number, NaN)的值,是各自不相等的。

要求 Eq 的一个示例,就是 HashMap<K, V> 中的那些键,如此 HashMap<K, V> 就可以区分出两个键是否一致。

用于排序比较的 PartialOrdOrd

PartialOrd and Ord for Ordering Comparisons

PartialOrd 特质实现为排序目的,而比较某种类型的那些实例。实现了 PartialOrd 的类型,便可与 <><=>= 符号一起使用了。咱们只能对那些同时实现了 PartialEq 的类型,应用这个 PartialOrd 特质。

派生 PartialOrd,会实现 partial_cmp 方法,该方法会返回一个在所给的那些值不会产生出顺序时,将为 None 的一个 Option<Ordering>。至于即使那种类型的大多数值都可被比较,但仍不会产生出顺序的值的一个示例,便是非数字(NaN)浮点值。在任何浮点数和非数字浮点值下调用 partial_cmp,都会返回 None

在于结构体上派生时,PartialOrd 会通过字段出现在结构体定义中的顺序,比较每个字段中的值,比较两个实例。而当于枚举上派生时,枚举定义中较早声明的枚举变种,被当作是小于后面所列出的那些变种的。

在比如会产生出由范围表达式所指定范围中一个随机数的, rand 代码箱的 gen_range 方法来说,PartialOrd 特质便是需要的。

Ord 特质实现对所注解类型的任何两个值,将存在有效顺序的掌握。Ord 特质会实现 cmp 方法,由于有效排序将始终可行,因此该方法返回的是 Ordering 而非 Option<Ordering>。咱们只可对那些同时实现了 PartialOrdEq (而 Eq 要求 PartialEq) 的类型,实现这个 Ord 特质。当于结构体及枚举上派生 Ord 时,cmp 就会以与 PartialOrdpartial_cmp 的派生实现同样方式行事。

要求 Ord 的一个示例,即为将一些值存储在 BTreeSet<T> 这种根据值的排序,而存储数据的数据结构中时。

用于复制值的 CloneCopy

Clone and Copy for Duplicating Values

Clone 特质实现了显式创建值的深拷贝而该复制过程则可能涉及运行一些任意代码arbitary code与拷贝内存堆数据。请参阅第 4 章中 “变量与数据交互方式:克隆” 小节,了解更多有关 Clone 的信息。

派生 Clone 会实现 clone 方法,当对整个类型实现了这个方法时,其就会在该类型的各个部分上调用 clone。这意味着类型要派生 Clone 其中的全部字段或值,都必须同时实现 Clone

需要 Clone 特质的一个示例,便是在切片上调用 to_vec 方法时。切片不持有其包含的那些类型实例,但自 to_vec 所返回的那个矢量值,却将需要持有他的那些实例,从而 to_vec 会调用各个条目上的 clone。因此,存储在切片中的类型,就必须实现 Clone

Copy 特质实现了只通过拷贝存储在栈上的二进制位,而复制某个值;任意代码并无必要。请参阅第 4 章中 “唯栈数据:拷贝”,了解更多有关 Copy 的信息。

Copy 特质没有定义阻止编程者过载那些方法,及破坏不会有任意代码运行这个假设的任何方法。那样的话,所有编程者就都可以假定,拷贝值将会非常快。

咱们可在其组成部分都实现了 Copy 的任何类型上派生 Copy 特质。由于实现 Copy 的类型,都有着执行与 Copy 同样任务的一个 Clone 的简单实现,因此实现 Copy 的类型必须同时实现 Clone

很少需要 Copy 特质;实现了 Copy 的类型,有着可供选择的优化方案,意味着咱们不必调用 clone,而调用 clone 会令到代码更简洁。

对于 Copy 下每种可能情况,咱们都可同时以 Clone 完成,除了代码可能更慢,或在一些地方不得不使用 clone

用于将值映射到固定大小值的 Hash

Hash for Mapping a Value to a Value of Fixed Size

Hash 特质实现了取某种任意大小类型的实例,并通过使用散列函数,将那个实例映射到固定大小的值。派生 Hash 会实现 hash 方法。hash 放的派生实现,会将在该类型各个组成部分上调用 hash 的结果结合起来,这就意味着类型要派生 Hash,那么其全部字段,都必须同时实现 Hash

要求 Hash 的一个示例,便是为了高效地存储数据,而在 Hash<K, V> 中存储那些键时。

用于默认值的 Default

Default for Default Values

Default 特质实现了为类型创建出一个默认值。派生 Default 会实现 default 函数。default 函数的派生实现,会在类型的各个部分上调用 default 函数,意味类型要派生 Defualt,其中的全部字段或值,都必须同时实现 Default

Default::default 函数,通常是与第 5 章中 “使用结构体更新语法从其他实例创建出实例” 小节里曾讨论过的结构体更新语法结合使用的。咱们可以定制结构体的几个字段,并在随后通过使用 ..Default::default(),为其余字段设置并使用默认值。

Option<T> 实例上使用 unwrap_or_default 方法时,便是需要 Default 特质的一个示例。当那个 Option<T>None 时,方法 unwrap_or_default 就将返回存储在 Option<T> 中,那个类型 TDefault::default 结果。

附录 D一些有用开发工具

在此附录中,咱们会讲到 Rust 项目所提供的一些有用的开发工具。咱们将看看自动格式化、应用警告修复的一些快速方法、一种代码静态分析工具a linter以及与多种 IDE 的集成。

使用 rustfmt 的自动格式化

Automatic Formatting with rustfmt

rustfmt 工具会依据社区编码风格,重新格式化咱们的代码。许多协作项目,都使用了 rustfmt 来防止有关编写 Rust 时使用何种风格方面的争论:每个人都使用这个工具来格式化他们的代码。

要安装 rustfmt,请键入下面的命令:

$ rustup component add rustfmt

如同 Rust 会同时给到 rustccargo 一样,此命令会给到咱们 rustfmtcargo-fmt。要格式化任何 Cargo 项目,请敲入下面的命令:

$ cargo fmt

运行此命令,会重新格式化当前代码箱中全部的 Rust 代码。这只会改变编码风格,而不会改变代码语义。关于 rustfmt 的更多信息,请参阅 其文档.

使用 rustfix 修复咱们的代码

Fix Your Code with rustfix

rustfix 工具已被 Rust 安装所包含,并可大致以咱们想要的方式,修复那些有着明确纠正问题方法的一些编译器告警。咱们之前大概率已经见到过编译器告警了。比如,设想有下面这段代码:

文件名:src/main.rs

fn do_something() {}

fn main() {
    for i in 0..100 {
        do_something();
    }
}

此处,咱们正调用 do_something 函数 100 次,但咱们在 for 循环的代码体中,从未用到那个变量 i。Rust 就会就此对咱们发出告警:

$ cargo build
   Compiling rustfix_demo v0.1.0 (/home/lenny.peng/rust-lang-zh_CN/rustfix_demo)
warning: unused variable: `i`
 --> src/main.rs:4:9
  |
4 |     for i in 0..100 {
  |         ^ help: if this is intentional, prefix it with an underscore: `_i`
  |
  = note: `#[warn(unused_variables)]` on by default

warning: `rustfix_demo` (bin "rustfix_demo") generated 1 warning
    Finished dev [unoptimized + debuginfo] target(s) in 0.29s

这个告警建议咱们要使用 _i 做名字:其中的下划线表示咱们有意不使用这个变量。通过运行 cargo fix 命令,咱们就可以使用 rustfix,自动应用那项建议:

$ cargo fix --allow-no-vcs
    Checking rustfix_demo v0.1.0 (/home/lenny.peng/rust-lang-zh_CN/rustfix_demo)
       Fixed src/main.rs (1 fix)
    Finished dev [unoptimized + debuginfo] target(s) in 0.17s

当咱们再次看到 src/main.rs,就将发现 cargo fix 已修改了这段代码:

文件名:src/main.rs

fn do_something() {}

fn main() {
    for _i in 0..100 {
        do_something();
    }
}

那个 for 循环变量,现在就被命名为了 _i,同时那条告警也不再出现了。

咱们还可使用 cargo fix 命令,将咱们的代码在不同 Rust 版本之间转换。有关这些 Rust 版本,在附录 E 中有讲到。

使用 Clippy 获得更多的代码静态分析

More Lints with Clippy

Clippy 工具是用于分析咱们代码,从而咱们可以捕获到一些常见错误,而改进咱们 Rust 代码的一套代码静态分析集合。

要安装 Clippy请输入以下命令

$ rustup component add Clippy

在任何 Cargo 项目上要运行 Clippy 的静态分析,请输入以下命令:

$ cargo clippy

比如说咱们编写了像下面这个程序这样,用到某个数学常量近似值,好比说 pi,的一个程序:

文件名:src/main.rs

fn main() {
    let x = 3.1415;
    let r = 8.0;
    println!("圆的面积为 {}", x * r * r);
}

在这个项目上运行 cargo clippy 就会得到下面的报错:

$ cargo clippy
    Checking clippy_demo v0.1.0 (/home/lenny.peng/rust-lang-zh_CN/clippy_demo)
error: approximate value of `f{32, 64}::consts::PI` found
 --> src/main.rs:2:13
  |
2 |     let x = 3.1415;
  |             ^^^^^^
  |
  = help: consider using the constant directly
  = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#approx_constant
  = note: `#[deny(clippy::approx_constant)]` on by default

error: could not compile `clippy_demo` due to previous error

此报错让咱们明白Rust 已经定义了一个更精确的 PI 常量,且当咱们使用这个常量时,咱们的程序将更为正确。那么咱们随后就应修改咱们代码为使用这个 PI 常量。下面的代码就捕获导致 Clippy 的任何错误或告警:

文件名:src/main.rs

fn main() {
    let x = std::f64::consts::PI;
    let r = 8.0;
    println!("圆的面积为 {}", x * r * r);
}

有关 Clippy 的更多信息,请参阅 其文档

用到 rust-analyzer 的 IDE 集成

IDE Integration Using rust-analyzer

为帮助 IDE 集成Rust 社区建议使用 rust-analyzer。此工具是一套以编译器为中心,操 语言服务器协议Language Server Protocol 的实用工具;而所谓语言服务器协议,则是用于各种 IDEs 和编程语言,二者相互之间通信的一种规格。有多种不同客户端可使用 rust-analyzer,比如 Visual Studio Code 的 Rust 分析器插件

请访问 rust-analyzer 项目 主页,了解其安全说明,随后在咱们的特定 IDE 中安装该语言的服务器支持。咱们的 IDE 就能获得诸如自动补全、跳至定义及行内报错等能力。

附录 E关于版本

Appendix E - Editions

在第一章中,咱们曾看到 cargo new 会把一点有关某个版的元数据,添加到咱们的 Cargo.toml 文件。此附录就会讲到那意味着什么!

Rust 语言及编译器有着六周的发布周期意味着用户会得到源源不断的新功能。其他编程语言会不经常地发布较大变更Rust 则会更频繁发布较小的更新。不久之后,全部这些小修改就会堆积起来。不过这一个个发布中,回头看看而讲到,“噢,从版本 1.10 到 1.31Rust 改变了很多!”。则是不容易的。

每两三年Rust 团队都会产生一个新的 Rust 版本edition。每个版本都会以完全更新的文档与工具,将那些业已落地到一个明确包中的特性放到一起。新版本会作为寻常的六周发布过程而交付。

这些版本服务了不同人群的不同目的:

  • 对于活跃的 Rust 用户,新版本会把那些增量变更,一起放入到一个易于掌握的包中;
  • 对于那些非用户,新版本释放了一些已落地的大进展信号,这会让 Rust 或许值得再看一看;
  • 对于开发 Rust 的人们,新版本会提供这个项目作为整体的集结点。

在本书编写时,已有三个 Rust 版本可用Rust 2015、Rust 2018 与 Rust 2021。本书是用 Rust 2021 版本的习惯用语编写的。

Cargo.toml 中的 edition表示应对咱们的代码使用哪个版本的编译器。若该键不存在Rust 就会以向后兼容原因,而使用 2015 作为版本值。

每个项目都可以选择一个不同于默认 2015 的版本。这些版本可能包含了不兼容的变更,比如包含了与代码中标识符冲突的新关键字。但是,除非咱们选到这些变更,那么即使咱们更新了所使用的 Rust 编译器,咱们的代码将继续编译。

全部 Rust 编译器版本,都会支持先于那个编译器发布而存在的任何版本,且他们可将任何受支持版本的代码箱连接起来。版本变更只会影响编译器于编译初期解析代码的方式。因此,当咱们正使用着 Rust 2015而咱们的一项依赖使用了 Rust 2018 时,咱们的项目将编译,并能够使用那项依赖。与之相反,在咱们的项目使用 Rust 2018而一项依赖使用了 Rust 2015 的情形下,也会工作。

要明确的是:绝大多数特性,在所有版本上都将可用。使用任何 Rust 版本的开发者,都将在新的稳定发布构造出来时,发现一些改进。但是,在一些情况下,主要是在新曾了关键字时,一些新特性就会只在稍后版本中可用了。若咱们打算利用上这些新特性,咱们将需要切换版本。

有关更多细节,版本指南Edition Guide 是本列举了不同版本间差异,并解释了怎样通过 cargo fix,而自动将咱们的代码更新到新版的一本完整的书。

附录 F - 本书的一些译本

<略>

附录 G - Rust 是怎样构造出来的与每日发布

How Rust is Made and "Nightly Rust"

此附录是有关 Rust 被怎样构造出来,及那会怎样作为一名 Rust 开发者的你。

强调稳定却并无止步不前

Stability Without Stagnation

作为一门语言Rust 在注重咱们代码稳定性方面 用心良苦。咱们希望 Rust 成为你可以在其上构建软件的稳固基础,而若那些物件都一直变动,那将是不可能实现的。而与此同时,若咱们无法实验一些新特性,那么直到这些特性发布后咱们不能在修改一些东西时,咱们也不会发现一些重大缺陷。

对于这个问题咱们Rust 团队)的解决方案就是咱们称作 “强调稳定又不要止步不前”而咱们的直到原则是这样的你永不必害怕升级到稳定的Rust 新版本。每次升级都应是无痛的,而又应带给你一些新特性、更少的程序错误,以及更快的编译时间。

啾,啾!发布通道与搭上快车

Choo, Choo! Release Channels and Riding the Trains

Rust 的开发,是运作在 火车时刻表train schedule 上的。那就是说,全部开发都是在 Rust 代码仓库的 master 分支上完成的。各个发布遵循了软件发布列车模型a software release train model该发布模型业已为 Cisco IOS 及其他软件项目所使用。Rust 有着以下三个 发布通道release channels

  • 每日发布nightly
  • Beta 发布beta
  • 稳定发布stable

多数 Rust 开发者主要使用稳定通道,而那些希望尝试实验性新特性的人们,则会使用每日发布或 beta 通道。

下面是个开发与发布流程运作方式的一个示例:咱们来假定 Rust 团队正工作于 Rust 1.5 的发布上。那个发布发生于 2015 年 11 月,但其将提供到我们实际版本数字。有个新特性被添加到 Rust一次新提交落在了 master 分支。每天晚上,都有一个新的 Rust 每日版本被产生出来。每天都是个发布日,而这些发布是由咱们的发布基础设施自动创建的。因此随着时间流逝,咱们的发布看起来就像下面这样,每晚一次:

nightly: * - - * - - *

每隔六周便是要准备一个新发布的时候了Rust 代码仓库的 beta 分支,便会从由每日发布所使用的 master 分支分叉开来。现在,就有了两个分支:

nightly: * - - * - - *
                     |
beta:                *

多数 Rust 使用者不会积极使用这些 beta 发布,但会在他们的 CI 系统中就 beta 发布加以测试,以帮助 Rust 发现可能出现的倒退。与此同时,仍有着每晚的每日发布:

nightly: * - - * - - * - - * - - *
                     |
beta:                *

在首个 beta 版创建出来六周后,就是稳定发布的时候了!stable 分支就被从 beta 分支创建出来:

nightly: * - - * - - * - - * - - * - - * - * - *
                     |
beta:                * - - - - - - - - *
                                       |
stable:                                *

Rust 1.5 便完成了!不过,咱们忘了一件事:由于这六个星期以及过去,而咱们还需要 Rust 下一 版本1.6,的一个新的 beta 发布。因此在 stale 分支从 beta 分支分叉出来后,下一版本的 beta 又会从 nightly 再度分叉出来:

nightly: * - - * - - * - - * - - * - - * - * - *
                     |                         |
beta:                * - - - - - - - - *       *
                                       |
stable:                                *

每六周就有一个发布 “离站”,但发布过程仍务必要在其抵达稳定发布前,经由这个 beta 通道行驶一段路程,由此这个过程便被称为 “列车模型”。

Rust 每六周发布,像时刻表一样。若咱们知道了一个 Rust 发布的日期,那么就能直到下一发布的日期:那便是六周后。每六周安排一次发布的一个好处,便是下一班列车很快就会到来。若某项特性刚好错过了某个特定发布,那么无需担心:另一发布将在不久后发生!这有助于减少在临近发布截止日期时,有可能未完善的功能偷偷潜入的压力。

归功于这个流程咱们可以始终检出check out下一构建的 Rust并自己验证到升级是容易的若 beta 发布没有如预期那样工作,咱们就可以将其报告给 Rust 团队并在下一稳定发布发生前修好他beta 发布中的损坏相对较少,但 rustc 仍属于一个软件,而确实存在一些错误。

不稳定特性

Unstable Features

这种发布模型下还有一个好处不稳定特性。Rust 使用了一种名为 “特性标识feature flags” 的技巧,来确定出给定发布中启用了哪些特性。若某项新特性处于活跃开发中,他就会落地在 master 分支上,而由此就会在每日发布中,但会有着一个 特性标识。而咱们作为用户希望尝试这个进展中的特性the work-in-progress feature咱们是可以尝试的但必须使用 Rust 的每日发布,并使用恰当的标识来注解咱们的代码,来选用该特性。

若咱们使用着 beta 或稳定发布的 Rust那么就不能使用任何特性标识。这是 Rust 团队在声明那些新特性永久稳定前,允许咱们实际用到他们的关键。希望选用最新特性的人们,便可这样做,而想要一种扎实体验的人,则可坚持使用稳定发布,而清楚他们的代码不会破坏。这便是稳定但并非止步不前。

由于那些工作中的特性仍在便会,且在本书写作时和他们在稳定构建中启用时,其间他们肯定将有所不同,因此本书只包含了那些稳定特性的信息。咱们可以在线上找到那些仅每日发布有的特性文档。

Rustup 与 Rust 每日发布所扮演的角色

Rustup and the Role of Rust Nightly

Rust 令到易于在全局或每个项目基础上,从不同发布通道的 Rust 之间改变。默认情况下,咱们将安装稳定发布的 Rust。而比如要安装每日发布

$ rustup toolchain install nightly

咱们也可以使用 rustup,查看全部的 工具链toolchains Rust 的各个发布与关联组件)。下面就是本书一位作者的 Windows 计算机上的示例:

> rustup toolchain list
stable-x86_64-pc-windows-msvc (default)
beta-x86_64-pc-windows-msvc
nightly-x86_64-pc-windows-msvc

在 Linux 系统上的输出如下:

$ rustup toolchain list
stable-x86_64-unknown-linux-gnu (default)

可以看到,稳定发布的工具链是默认的。绝大多数 Rust 用户会在多数时候使用稳定发布。咱们可能想要在多数时候使用稳定发布,又因为咱们关心某项最新特性,而会在特定项目使用每日发布。要这样做,就可以在那个项目目录下,使用 rustup override 来将每日发布工具链,设置为当咱们位处那个目录中时,rustup 使用的那个工具链:

$ cd ~/projects/needs-nightly
$ rustup override set nightly

现在,当咱们每次在 ~/projects/needs-nightly 目录下调用 rustccargo 时,rustup 都会确保咱们在使用每日发布的 Rust而非咱们默认的稳定发布 Rust 了。再有很多 Rust 项目时,这就会排上用场!

请求评议流程与各种团队

The RFC Process and Teams

那么咱们该怎么了解到这些新特性呢Rust 的开发模型,遵循了 请求评议流程Request For Comments(RFC) process。如你想要 Rust 的一项改进那么就可以编写一个名为请求评议RFC 的提议。

人人都可以编写请求评议来改进 Rust同时这些提议会经过由许多议题子团队所组成的 Rust 团队审阅和讨论。在 Rust 网站上 有这些团队的完整清单,其中包括了该项目各领域:语言设计、编译器实现、基础设施、文档及其他等的团队。恰当的团队会阅读提议与评论,撰写出他们自己的一些评论,并在最后,便有了接受或拒绝该特性的共识。

若该特性被接受了,就会在 Rust 代码仓库上开出一个 issue同时某个人就可以实现他。将其实现得非常棒的那个人可能不是最早提议这项特性的那人在实现准备好时其就会落地于 master 分支的特性门a feature gate之后如同咱们曾在 “不稳定特性” 小节中曾讨论过的那样。

过了一段时间后,一旦那些用到每日发布的 Rust 开发者们,能够试用这项新特性,那么 Rust 团队成员将讨论这项特性,怎样将其编制到每日发布上,并决定其是否有那个被构造到稳定发布 Rust。而若决定是继续推进那么特性门就会被移除同时这项特性就被认为是稳定的了他就会搭上列车进到一个新的稳定发布 Rust 中。

附录 H - 有用笔记

此处记录学习及应用 Rust 编程软件过程中,觉得有用的一些东西。

cargo-binutils

这个项目 是 Embbeded-Rust 项目的,而不是 Rust 官方的,但提供了有用的功能。比如查看构建出的二进制程序文件的那些头部:

$ cargo readobj --bin clippy_demo -- --file-headers
    Finished dev [unoptimized + debuginfo] target(s) in 0.00s
ELF Header:
  Magic:   7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00
  Class:                             ELF64
  Data:                              2's complement, little endian
  Version:                           1 (current)
  OS/ABI:                            UNIX - System V
  ABI Version:                       0
  Type:                              DYN (Shared object file)
  Machine:                           Advanced Micro Devices X86-64
  Version:                           0x1
  Entry point address:               0x86D0
  Start of program headers:          64 (bytes into file)
  Start of section headers:          4305200 (bytes into file)
  Flags:                             0x0
  Size of this header:               64 (bytes)
  Size of program headers:           56 (bytes)
  Number of program headers:         12
  Size of section headers:           64 (bytes)
  Number of section headers:         42
  Section header string table index: 41

使用前需要进行如下安装:

$ cargo install cargo-binutils
$ rustup component add llvm-tools-preview

附录 I - 术语清单

  • 单态化

所谓 单态化monomorphization,是指即通过把在编译后用到的具体类型填入到泛型位置,而将通用代码转换为具体代码的过程。参考 使用泛型代码的性能问题

  • 内聚属性

a property called coherence,参见 在类型上实现某个特质

  • 孤儿规则

the orphan rule, 参见 在类型上实现某个特质

  • impl Trait 语法

impl Trait syntax, 在函数参数清单中,将特质用作参数类型注解的语法。参见:作为参数的特质

  • 特质边界语法

Trait bound syntax, 参见 特质边界语法

  • 语法糖

Sugar syntax, 参见 特质边界语法

  • 指明多个特质边界的 + 语法

The + syntax for specifying multiple trait bounds, 参见:使用 + 语法,指定多个特质边界

  • where 子句

where clauses, 参见

  • 生命周期省略规则

Lifetime elision rules, 编程到 Rust 引用分析中的一些确定性模式。

  • 输入生命周期

Input lifetimes函数或方法上的生命周期

  • 输出生命周期

Output lifetimes, 返回值上的生命周期