-
Rust 程序设计语言
-
前言
-
简介
-
1.
入门指南
-
-
1.1.
安装
-
1.2.
Hello, World!
-
1.3.
Hello, Cargo!
-
2.
写个猜数字游戏
-
3.
常见编程概念
-
-
3.1.
变量与可变性
-
3.2.
数据类型
-
3.3.
函数
-
3.4.
注释
-
3.5.
控制流
-
4.
认识所有权
-
-
4.1.
什么是所有权?
-
4.2.
引用与借用
-
4.3.
Slice 类型
-
5.
使用结构体组织相关联的数据
-
-
5.1.
结构体的定义和实例化
-
5.2.
结构体示例程序
-
5.3.
方法语法
-
6.
枚举和模式匹配
-
-
6.1.
枚举的定义
-
6.2.
match 控制流结构
-
6.3.
if let 简洁控制流
-
7.
使用包、Crate 和模块管理不断增长的项目
-
-
7.1.
包和 Crate
-
7.2.
定义模块来控制作用域与私有性
-
7.3.
引用模块项目的路径
-
7.4.
使用 use 关键字将路径引入作用域
-
7.5.
将模块拆分成多个文件
-
8.
常见集合
-
-
8.1.
使用 Vector 储存列表
-
8.2.
使用字符串储存 UTF-8 编码的文本
-
8.3.
使用 Hash Map 储存键值对
-
9.
错误处理
-
-
9.1.
用 panic! 处理不可恢复的错误
-
9.2.
用 Result 处理可恢复的错误
-
9.3.
要不要 panic!
-
10.
泛型、Trait 和生命周期
-
-
10.1.
泛型数据类型
-
10.2.
Trait:定义共同行为
-
10.3.
生命周期确保引用有效
-
11.
编写自动化测试
-
-
11.1.
如何编写测试
-
11.2.
控制测试如何运行
-
11.3.
测试的组织结构
-
12.
一个 I/O 项目:构建命令行程序
-
-
12.1.
接受命令行参数
-
12.2.
读取文件
-
12.3.
重构以改进模块化与错误处理
-
12.4.
采用测试驱动开发完善库的功能
-
12.5.
处理环境变量
-
12.6.
将错误信息输出到标准错误而不是标准输出
-
13.
Rust 中的函数式语言功能:迭代器与闭包
-
-
13.1.
闭包:可以捕获其环境的匿名函数
-
13.2.
使用迭代器处理元素序列
-
13.3.
改进之前的 I/O 项目
-
13.4.
性能比较:循环对迭代器
-
14.
更多关于 Cargo 和 Crates.io 的内容
-
-
14.1.
采用发布配置自定义构建
-
14.2.
将 crate 发布到 Crates.io
-
14.3.
Cargo 工作空间
-
14.4.
使用 cargo install 安装二进制文件
-
14.5.
Cargo 自定义扩展命令
-
15.
智能指针
-
-
15.1.
使用 Box<T> 指向堆上数据
-
15.2.
使用 Deref Trait 将智能指针当作常规引用处理
-
15.3.
使用 Drop Trait 运行清理代码
-
15.4.
Rc<T> 引用计数智能指针
-
15.5.
RefCell<T> 与内部可变性模式
-
15.6.
引用循环会导致内存泄漏
-
16.
无畏并发
-
-
16.1.
使用线程同时地运行代码
-
16.2.
使用消息传递在线程间通信
-
16.3.
共享状态并发
-
16.4.
使用 Sync 与 Send Traits 的可扩展并发
-
17.
Rust 的面向对象编程特性
-
-
17.1.
面向对象语言的特点
-
17.2.
顾及不同类型值的 trait 对象
-
17.3.
面向对象设计模式的实现
-
18.
模式与模式匹配
-
-
18.1.
所有可能会用到模式的位置
-
18.2.
Refutability(可反驳性): 模式是否会匹配失效
-
18.3.
模式语法
-
19.
高级特征
-
-
19.1.
不安全的 Rust
-
19.2.
高级 trait
-
19.3.
高级类型
-
19.4.
高级函数与闭包
-
19.5.
宏
-
20.
最后的项目:构建多线程 web server
-
-
20.1.
建立单线程 web server
-
20.2.
将单线程 server 变为多线程 server
-
20.3.
优雅停机与清理
-
21.
附录
-
-
21.1.
A - 关键字
-
21.2.
B - 运算符与符号
-
21.3.
C - 可派生的 trait
-
21.4.
D - 实用开发工具
-
21.5.
E - 版本
-
21.6.
F - 本书译本
-
21.7.
G - Rust 是如何开发的与 “Nightly Rust”
commit a371f82b0916cf21de2d56bd386ca5d72f7699b0
结构体和我们在
“元组类型”
部分论过的元组类似,它们都包含多个相关的值。和元组一样,结构体的每一部分可以是不同类型。但不同于元组,结构体需要命名各部分数据以便能清楚的表明其值的意义。由于有了这些名字,结构体比元组更灵活:不需要依赖顺序来指定或访问实例中的值。
定义结构体,需要使用
struct
关键字并为整个结构体提供一个名字。结构体的名字需要描述它所组合的数据的意义。接着,在大括号中,定义每一部分数据的名字和类型,我们称为
字段
(
field
)。例如,示例 5-1 展示了一个存储用户账号信息的结构体:
文件名:src/main.rs
struct User {
active: bool,
username: String,
email: String,
sign_in_count: u64,
fn main() {}
示例 5-1:
User
结构体定义
一旦定义了结构体后,为了使用它,通过为每个字段指定具体值来创建这个结构体的
实例
。创建一个实例需要以结构体的名字开头,接着在大括号中使用
key: value
键 - 值对的形式提供字段,其中 key 是字段的名字,value 是需要存储在字段中的数据值。实例中字段的顺序不需要和它们在结构体中声明的顺序一致。换句话说,结构体的定义就像一个类型的通用模板,而实例则会在这个模板中放入特定数据来创建这个类型的值。例如,可以像示例 5-2 这样来声明一个特定的用户:
文件名:src/main.rs
struct User {
active: bool,
username: String,
email: String,
sign_in_count: u64,
fn main() {
let user1 = User {
active: true,
username: String::from("someusername123"),
email: String::from("[email protected]"),
sign_in_count: 1,
示例 5-2:创建 User
结构体的实例
为了从结构体中获取某个特定的值,可以使用点号。举个例子,想要用户的邮箱地址,可以用 user1.email
。如果结构体的实例是可变的,我们可以使用点号并为对应的字段赋值。示例 5-3 展示了如何改变一个可变的 User
实例中 email
字段的值:
文件名:src/main.rs
struct User {
active: bool,
username: String,
email: String,
sign_in_count: u64,
fn main() {
let mut user1 = User {
active: true,
username: String::from("someusername123"),
email: String::from("[email protected]"),
sign_in_count: 1,
user1.email = String::from("[email protected]");
示例 5-3:改变 User
实例 email
字段的值
注意整个实例必须是可变的;Rust 并不允许只将某个字段标记为可变。另外需要注意同其他任何表达式一样,我们可以在函数体的最后一个表达式中构造一个结构体的新实例,来隐式地返回这个实例。
示例 5-4 显示了一个 build_user
函数,它返回一个带有给定的 email 和用户名的 User
结构体实例。active
字段的值为 true
,并且 sign_in_count
的值为 1
。
文件名:src/main.rs
struct User {
active: bool,
username: String,
email: String,
sign_in_count: u64,
fn build_user(email: String, username: String) -> User {
User {
active: true,
username: username,
email: email,
sign_in_count: 1,
fn main() {
let user1 = build_user(
String::from("[email protected]"),
String::from("someusername123"),
示例 5-4:build_user
函数获取 email 和用户名并返回 User
实例
为函数参数起与结构体字段相同的名字是可以理解的,但是不得不重复 email
和 username
字段名称与变量有些啰嗦。如果结构体有更多字段,重复每个名称就更加烦人了。幸运的是,有一个方便的简写语法!
因为示例 5-4 中的参数名与字段名都完全相同,我们可以使用 字段初始化简写语法(field init shorthand)来重写 build_user
,这样其行为与之前完全相同,不过无需重复 username
和 email
了,如示例 5-5 所示。
文件名:src/main.rs
struct User {
active: bool,
username: String,
email: String,
sign_in_count: u64,
fn build_user(email: String, username: String) -> User {
User {
active: true,
username,
email,
sign_in_count: 1,
fn main() {
let user1 = build_user(
String::from("[email protected]"),
String::from("someusername123"),
示例 5-5:build_user
函数使用了字段初始化简写语法,因为 username
和 email
参数与结构体字段同名
这里我们创建了一个新的 User
结构体实例,它有一个叫做 email
的字段。我们想要将 email
字段的值设置为 build_user
函数 email
参数的值。因为 email
字段与 email
参数有着相同的名称,则只需编写 email
而不是 email: email
。
使用旧实例的大部分值但改变其部分值来创建一个新的结构体实例通常是很有用的。这可以通过 结构体更新语法(struct update syntax)实现。
首先,示例 5-6 展示了不使用更新语法时,如何在 user2
中创建一个新 User
实例。我们为 email
设置了新的值,其他值则使用了实例 5-2 中创建的 user1
中的同名值:
文件名:src/main.rs
struct User {
active: bool,
username: String,
email: String,
sign_in_count: u64,
fn main() {
// --snip--
let user1 = User {
email: String::from("[email protected]"),
username: String::from("someusername123"),
active: true,
sign_in_count: 1,
let user2 = User {
active: user1.active,
username: user1.username,
email: String::from("[email protected]"),
sign_in_count: user1.sign_in_count,
示例 5-6:使用 user1
中的一个值创建一个新的 User
实例
使用结构体更新语法,我们可以通过更少的代码来达到相同的效果,如示例 5-7 所示。..
语法指定了剩余未显式设置值的字段应有与给定实例对应字段相同的值。
文件名:src/main.rs
struct User {
active: bool,
username: String,
email: String,
sign_in_count: u64,
fn main() {
// --snip--
let user1 = User {
email: String::from("[email protected]"),
username: String::from("someusername123"),
active: true,
sign_in_count: 1,
let user2 = User {
email: String::from("[email protected]"),
..user1
示例 5-7:使用结构体更新语法为一个 User
实例设置一个新的 email
值,不过其余值来自 user1
变量中实例的字段
示例 5-7 中的代码也在 user2
中创建了一个新实例,但该实例中 email
字段的值与 user1
不同,而 username
、 active
和 sign_in_count
字段的值与 user1
相同。..user1
必须放在最后,以指定其余的字段应从 user1
的相应字段中获取其值,但我们可以选择以任何顺序为任意字段指定值,而不用考虑结构体定义中字段的顺序。
请注意,结构更新语法就像带有 =
的赋值,因为它移动了数据,就像我们在“变量与数据交互的方式(一):移动”部分讲到的一样。在这个例子中,总体上说我们在创建 user2
后不能就再使用 user1
了,因为 user1
的 username
字段中的 String
被移到 user2
中。如果我们给 user2
的 email
和 username
都赋予新的 String
值,从而只使用 user1
的 active
和 sign_in_count
值,那么 user1
在创建 user2
后仍然有效。active
和 sign_in_count
的类型是实现 Copy
trait 的类型,所以我们在“变量与数据交互的方式(二):克隆” 部分讨论的行为同样适用。
也可以定义与元组(在第三章讨论过)类似的结构体,称为 元组结构体(tuple structs)。元组结构体有着结构体名称提供的含义,但没有具体的字段名,只有字段的类型。当你想给整个元组取一个名字,并使元组成为与其他元组不同的类型时,元组结构体是很有用的,这时像常规结构体那样为每个字段命名就显得多余和形式化了。
要定义元组结构体,以 struct
关键字和结构体名开头并后跟元组中的类型。例如,下面是两个分别叫做 Color
和 Point
元组结构体的定义和用法:
文件名:src/main.rs
struct Color(i32, i32, i32);
struct Point(i32, i32, i32);
fn main() {
let black = Color(0, 0, 0);
let origin = Point(0, 0, 0);
注意 black
和 origin
值的类型不同,因为它们是不同的元组结构体的实例。你定义的每一个结构体有其自己的类型,即使结构体中的字段可能有着相同的类型。例如,一个获取 Color
类型参数的函数不能接受 Point
作为参数,即便这两个类型都由三个 i32
值组成。在其他方面,元组结构体实例类似于元组,你可以将它们解构为单独的部分,也可以使用 .
后跟索引来访问单独的值,等等。
我们也可以定义一个没有任何字段的结构体!它们被称为 类单元结构体(unit-like structs)因为它们类似于 ()
,即“元组类型”一节中提到的 unit 类型。类单元结构体常常在你想要在某个类型上实现 trait 但不需要在类型中存储数据的时候发挥作用。我们将在第十章介绍 trait。下面是一个声明和实例化一个名为 AlwaysEqual
的 unit 结构的例子。
文件名:src/main.rs
struct AlwaysEqual;
fn main() {
let subject = AlwaysEqual;
为了定义 AlwaysEqual
,我们使用 struct
关键字,接着是我们想要的名称,然后是一个分号。不需要花括号或圆括号!然后,我们可以以类似的方式在 subject
变量中创建 AlwaysEqual
的实例:只需使用我们定义的名称,无需任何花括号或圆括号。设想我们稍后将为这个类型实现某种行为,使得每个 AlwaysEqual
的实例始终等于任何其它类型的实例,也许是为了获得一个已知的结果以便进行测试。我们无需要任何数据来实现这种行为!在第十章中,你会看到如何定义特征并在任何类型上实现它们,包括类单元结构体。
在示例 5-1 中的 User
结构体的定义中,我们使用了自身拥有所有权的 String
类型而不是 &str
字符串 slice 类型。这是一个有意而为之的选择,因为我们想要这个结构体拥有它所有的数据,为此只要整个结构体是有效的话其数据也是有效的。
可以使结构体存储被其他对象拥有的数据的引用,不过这么做的话需要用上 生命周期(lifetimes),这是一个第十章会讨论的 Rust 功能。生命周期确保结构体引用的数据有效性跟结构体本身保持一致。如果你尝试在结构体中存储一个引用而不指定生命周期将是无效的,比如这样:
文件名:src/main.rs
struct User {
active: bool,
username: &str,
email: &str,
sign_in_count: u64,
fn main() {
let user1 = User {
active: true,
username: "someusername123",
email: "[email protected]",
sign_in_count: 1,
编译器会抱怨它需要生命周期标识符:
$ cargo run
Compiling structs v0.1.0 (file:///projects/structs)
error[E0106]: missing lifetime specifier
--> src/main.rs:3:15
3 | username: &str,
| ^ expected named lifetime parameter
help: consider introducing a named lifetime parameter
1 ~ struct User<'a> {
2 | active: bool,
3 ~ username: &'a str,
error[E0106]: missing lifetime specifier
--> src/main.rs:4:12
4 | email: &str,
| ^ expected named lifetime parameter
help: consider introducing a named lifetime parameter
1 ~ struct User<'a> {
2 | active: bool,
3 | username: &str,
4 ~ email: &'a str,
For more information about this error, try `rustc --explain E0106`.
error: could not compile `structs` due to 2 previous errors
第十章会讲到如何修复这个问题以便在结构体中存储引用,不过现在,我们会使用像 String
这类拥有所有权的类型来替代 &str
这样的引用以修正这个错误。