Basic Syntax¶
约 2745 个字 153 行代码 预计阅读时间 11 分钟
1. Variables and Mutability¶
2. Data Types¶
3. Functions¶
4. Control Flow¶
5. Basic Collections (Vectors)¶
6. Strings and Text¶
7. Hash Maps¶
一、变量¶
1 命名惯例¶
条目 | 惯例 |
---|---|
模块 Modules | snake_case |
类型 Types | UpperCamelCase |
特征 Traits | UpperCamelCase |
枚举 Enumerations | UpperCamelCase |
结构体 Structs | UpperCamelCase |
函数 Functions | snake_case |
方法 Methods | snake_case |
通用构造器 General constructors | new or with_more_details |
转换构造器 Conversion constructors | from_some_other_type |
宏 Macros | snake_case! |
局部变量 Local variables | snake_case |
静态类型 Statics | SCREAMING_SNAKE_CASE |
常量 Constants | SCREAMING_SNAKE_CASE |
类型参数 Type parameters | UpperCamelCase ,通常使用一个大写字母: T |
生命周期 Lifetimes | 通常使用小写字母: 'a ,'de ,'src |
2 绑定与解构¶
使用 let
关键字将值绑定到一个变量上,变量绑定默认是不可变的,如果需要可变绑定,使用 let mut
关键字。
使用 let
关键字还可以进行复杂变量的解构,这也就是从一个相对复杂的变量之中,匹配出这个变量的一部分内容。
3 所有权与引用¶
可变引用允许我们修改借用来的值,而不需要获取该值的所有权。首先要求我们声明变量为 mut
,然后使用 &mut
关键字来创建一个可变引用。
Rust 严格限制可变引用,借以有效避免数据竞争。如果以下三个条件同时满足,则会发生数据竞争:
- 两个或更多的指针同时访问同一数据;
- 至少有一个指针被用来写入数据;
- 没有同步数据访问的机制。
Rust 要求在同一个作用域之中,一个数据只能有一个可变引用,同时,可变引用和不可变引用不能同时存在,但是多个不可变引用同时存在是可以的。显然,Rust 通过避免第一条,通过限制可变引用的数量,以及同一时间只能有一个可变引用的规则,避免了数据竞争。
二、基础类型系统¶
1 基础类型¶
- 整数类型:
- 浮点类型:
- 布尔类型:
- 字符类型:
2 复合类型¶
元组:A finite heterogeneous sequence。有限长,可以包含各种类型,可以通过 index 访问。逗号分割,圆括号包裹,长度固定,在尺寸上不能增长也不能缩水,可以析构,直接使用 .index
访问:
tup
被绑定到整个元组上,因为元组被视为是一个单独的复合元素。空元组 ()
被称为 unit,其值和类型都为 ()
。表达式如果不返回任何值,则隐式返回 ()
。
数组:A fixed-size array。长度固定,里边类型相同,直接使用 [type; length]
声明,使用 [index]
访问,使用 [ele1, ele2, ...]
或者 [exp; length]
初始化,其中 exp
要么是实现了 Copy
特征的类型,要么是一个 const
类型;
数组在栈中分配内存,长度固定,不能增长或缩水,如果需要动态大小的数组,可以使用 Vec
类型。违法的索引会引起 Runtime Error。
切片:A dynamically-sized view into a contiguous sequence。对于定义为 [T; n]
的数组,其切片类型为 [T]
,对应的内存大小在编译时期不确定,因此不能直接使用。对应的引用类型为 &[T]
,切片引用事实上更为常见,因为其长度在编译时期确定,事实上被实现为胖指针,包含一个指向数据的指针,以及一个长度信息。
3 结构体¶
结构体:A type that is composed of other types。和元组类似,可以存储多个相关值,并且各个字段可以为不同类型,但是结构体的各个字段都是命名的。结构体使用 struct
关键字声明,使用花括号包裹,字段间使用逗号分割,字段名和类型间使用冒号分割。使用 .name
访问字段。
创建实例的时候,指定结构体名称并且添加包含键值对的花括号,实例化的时候不要求顺序一致。结构体的实例必须整体标记为 mut
,不能单独指定字段是可变的。
可以使用 ..another_instance
来从其余实例中复制其余字段,但是其必须最后出现。
注意这样的更新语法使用的是赋值 =
,因此会发生所有权转移,这样 user1
的 username
字段的所有权就转移给了 user2
,而 user1
的 email
字段就无效了,我们因此也不能再整体使用 user1
了。
元组结构体:
单元结构体:
方法:
关联函数:所有在 impl
块中定义的函数被称为关联函数/Associated Functions,因为其与对应的类型相关。我们知道,所谓方法是以 self
作为第一个参数的函数,而关联函数则不以 self
作为第一个参数,因此常常被使用在构造器上。
其中关键词 Self
表示类型本身,也就是 Rectangle
类型,我们也使用结构体名和 ::
语法来调用对应的关联函数,比如 let sq = Rectangle::square(3);
。
4 枚举¶
A type that can be any one of several variants. 如果我们想定义一个操作/类型的集合,并且每个元素都有自己的字段,那么枚举是一个很好的选择。
5 Vector¶
A contiguous growable array type, written as Vec<T>
, short for ‘vector’。
6 String¶
A UTF-8 encoded, growable string.
7 HashMap¶
Rust 中的哈希映射集合类型,用于存储键值对映射关系,类型表示为 HashMap<K, V>
,其中 K
是键的类型,V
是值的类型。
- 需要使用
use std::collections::HashMap;
导入 HashMap 类型。 - HashMap 和 Vec 类似,是同质的并且数据保存在堆上。
- 通过
.get()
方法查询键值对,返回Option<&V>
类型,如果键不存在,则返回None
,对于返回的Option<&V>
类型,可以使用.copied()
方法使之成为Option<V>
类型,再使用.unwrap_or()
方法返回默认值或者原本值。 - 通过
for
循环遍历键值对,返回(&K, &V)
类型。 - 对于实现了
Copy
特征的类型,在插入的时候,其值会自动拷贝进 HashMap 中,对于拥有所有权的类型,其值会被移动到 HashMap 中。当引用类型被插入到 HashMap 中的时候,我们必须保证其引用的值在 HashMap 的有效期之内不会失效。 - 如果键值对已经存在,那么插入的值会覆盖原有的值。
.entry()
方法会返回一个 Entry
类型,其是一个枚举类型,包含 Occupied
和 Vacant
两种情况,分别表示键存在和键不存在。.entry(key).or_insert(value)
则是如果键存在,则不进行任何操作,如果键不存在,则插入对应的值,并且返回该值的可变引用。同理还有 .entry(key).and_modify(f)
方法,如果键存在,则执行 f
函数,如果键不存在,则返回 Vacant
类型。
借用 .entry().or_insert()
方法返回的引用,可以修改键对应的值:
三、函数,控制流与模式匹配¶
1 函数¶
2 控制流¶
3 模式匹配¶
match
允许我们将一个值和一系列的类型相匹配,类型可以是变量、字面量、通配符或者其余模式。
if let
可以认为是 match
的一种语法糖,只匹配一个模式,其余模式完全忽略。运行模式也几乎一样:if let
接收一个模式和一个表达式,用等号分割,如果模式匹配,则进行绑定并且执行对应代码块,否则执行 else
分支(或者没有这个分支)。
let else
是另外一种比较新的语法。和 if let
不同的是,let else
如果模式匹配,那么直接进行绑定,没有对应的代码快,如果不匹配,那么直接进行返回。下面进行一个简单的比较:
能写出第一种的都是神人了。if let
和 let else
都有语法简洁,缩进较少的特点,尤其 let else
在不匹配的时候直接终止流程,并且可以在更大作用域上绑定值,所以更加易用。
四、特征与泛型¶
五、模块系统与并发¶
1 箱与包¶
箱/Crate:Rust 编译器编译的最小单元,一个由模块组成的树状结构。箱可以包含模块,模块可能被定义在别的文件中,分为两种形式:
- 二进制箱/Binary Crate:可以编译为可执行文件的程序。必须有一个
main
函数定义执行行为。 - 库箱/Library Crate:不具有
main
函数,不编译为可执行文件,但是定义了可以在多个项目中共享的功能。
箱根/Crate Root:是编译器开始编译的源文件,构成箱的根模块。
包/Package:提供一组功能的一个或者多个箱。一个包包含了一个 Cargo.toml
文件,描述如何构建这个包。Cargo 其实就是一个包,其包含了构建代码的命令行工具(二进制箱)及其库箱。一个包可以包含任意多个二进制箱,但是至多有一个库箱。
如果我们运行 cargo new
命令,会生成一个包,其内当然包含 Cargo.toml
文件,描述如何构建这个包。我们遵循如下规定:
src/main.rs
是与包同名的二进制箱的箱根;src/lib.rs
(如果存在)是与包同名的库箱的箱根。- 可以在
src/bin
目录下放置文件来创建多个二进制箱,每个文件都是一个独立的二进制箱。
2 模块¶
模块/Modules 是 Rust 中组织代码的单元,
编译箱的基本规则大致如下:
- 编译器首先检查箱根文件,通常是上文提到的
src/main.rs
或者src/lib.rs
; - 声明模块:在箱根文件中,可以声明新的模块
mod module_name;
,编译器会在下面三个地方检查其定义:内连方式,也就是使用花括号代替分号;文件方式,会查找src/module_name.rs
文件;目录方式,会查找src/module_name
目录,该目录下必须有一个mod.rs
文件。 - 声明子模块:在非箱根文件中,比如在
src/module_name.rs
中,可以声明子模块mod submodule_name;
,编译器会按照上述规则递归检查其定义:内连方式同理;文件方式,会查找src/module_name/submodule_name.rs
文件;目录方式,会查找src/module_name/submodule_name
目录,该目录下必须有一个mod.rs
文件。 - 代码路径:比如
crate::module_name::submodule_name::type
表示在子模块中的type
类型。 use
关键字:在作用域内,创建对应条目的快捷方式,比如use crate::module_name::submodule_name::type;
表示我们引入了type
类型,之后只需要写type
即可。pub
关键字:模块中的代码默认对其父模块私有,使用pub mod
可以将其声明为公开的,如果要是使里面的某个条目公开,则需要在其对应的声明前面加上pub
关键字。