语言基础
基本语法
数据类型
操作符
操作符的本质是一些内置函数的语法糖,允许开发者使用简洁的符号调用一个全局可见的、常用的函数。
流程控制
if-else
while/for/loop
模式匹配 match
模式匹配是一种高级的 if-else 语句和 switch 的语法糖,它支持匹配一个变量的类型、结构,并提取和解构一个变量,相较于 if-else 更加方便,同时简化了丑陋的 switch 语句。
函数
面向数据 + 方法绑定
面向对象是一个火爆的特性,常见的语言几乎都或多或少地支持。Rust 中采取的是函数式编程语言中的思路,提供一种使用面向数据和方法绑定的机制来实现面向对象的封装,但是不支持继承。这种将数据和行为分离的书写方式,相较于直接在类里面定义数据和方法的写法,更加强调数据面和管理面的分离,同时也易于扩展某个数据类型的行为,同时默认将面向对象中的组合概念进行深刻地贯彻。
C 语言中不支持面向对象的特性,但是提供基础的面向数据和面向过程的能力,在 C 语言中,允许用户定义 struct 结构来封装一个复杂的纯数据,并且 C 的开发这会书写一系列以该结构体为第一个参数的函数,从而来模拟方法绑定的效果。
Go 语言中,也提供了类似 Rust 的面向数据 + 方法绑定的方式来管理一些复杂的数据类型
结构体
在 rust 中,使用 struct 关键字定义一个纯数据结构,同时可以使用 enum 关键字定义一类纯数据结构。struct 可以是字典结构体,也可以是一个元组结构体,
struct MyDataStruct {
name: String,
age: u32
}
struct MyPointTuple(i32, i32, i32);
struct MyDataEmpty;
enum MyEnum {
TypeEmpty,
TypeTuple(i32, i32, i32),
TypeStruct {
name: String,
age: u32
},
}impl 伴生结构体
trait(特性)vs interface(接口)
在 Rust 中,提供一种名叫 trait 的机制来实现类似面向对象中的 interface 的功能,
内置 trait
内置的 trait 作为语言的基础设施,广泛用于实现大家经常遇到的功能和需求,规定语言中的一些标准,甚至能够影响编译器的编译行为。
泛型
泛型是一个迫切求值类型函数,它可以通过泛型函数自动生成多种组合类型,其表现为一个数据类型的参数,通过 Type<T> 中的尖括号进行声明和调用。
数据类型包括简单类型和复杂类型,如 i32、f64,复杂类型使用 struct、enum、union 进行定义;
rust 具有强大的泛型系统,它支持类型修饰、泛型组合、关联类型等强大特性。
类型修饰
在数据类型的基础上对类型进行额外修饰,这些修饰包括:
- 可变引用修饰与不可变引用修饰,&、&mut
- 可变裸指针修饰和不可变裸指针修饰,*mut、*const
- 生命周期修饰,'a、'static
- 动态派发修饰,dyn trait_name
泛型组合 trait bound
关联类型
在 Haskell 等函数式的语言中,存在一种名叫高阶类型的特性,高阶类型对标的是高阶函数,普通的函数接受值然后输出值,高阶函数接受函数并且可以输出新函数,高阶类型接受高阶类型并可以输出新的高阶类型。
| 值空间 | 类型空间 |
|---|---|
| 值 | 类型 |
| 二等函数 | 泛型 |
| 一等函数+高阶函数 | 一等类型+高阶类型 |
| 闭包 | ? |
二等函数和一等函数区别在于,一等函数可以被看做数据一样被传递,二等函数不能,因为二等函数是迫切求值的,它必须以调用的形式出现,而不能被当作值来使用和传递;闭包意味着能够动态生成一个函数,并且引用局部作用域中的变量。泛型意味着可以使用泛型生成一个新的类型,但是泛型本身不能作为类型来传递,因为泛型是迫切求值的。
在 rust 中,没有选择实现高阶类型,但是选择实现了关联类型,来在一定程度上解决泛型迫切求值的问题。
属性标记和宏
宏是生成代码的代码,特别是对于不能够进行动态解释的语言,为了简化编写代码的复杂性,使用宏可以加速我们的开发。
所有权和借用机制
所有权
在 rust 中,所有权是指一个保存在内存中的数据,需要有且仅有一个所有者。所谓所有者,本质就是一个指针,通常表现为一个变量名,或者是一个实例化对象的名字符号;
- 每个值都有且只有一个所有者,但是一个值的所有者可以改变,也就是移动语义;
- 当所有者离开作用域时,其拥有的值会被释放;
- 赋值操作具有默认的移动语义,即旧变量赋值给新变量时,旧变量不再拥有该值;
借用机制意味着,一个值可以被指针 A 所有,但却可以被 B 借用,其核心规则是任意时刻,一个值只能有一个可变指针指向该值,或者同时有多个不可变指针指向该值,两种情况选择其一;
- 一个可变指针
- 多个不可变指针
移动和借用
移动,说明一个值的所有权发生了转移,原先的指针失效了,不能再通过原指针访问数据;借用,意味着新建了一个新的指针指向了该值,但是这个指针是一个引用,引用类型和原类型相差一个 & 符号,他们都属于指针,不带 & 是所有者,带 & 是借用者。
隐式(所有权)移动
在 rust 中,除了赋值符号 =,当对函数传入参数的时候、还有函数结束并返回值的时候、还有声明闭包函数的时候,同样也会发生所有权的移动,除非将函数的参数或者返回值声明为一个 & 符号的引用,这表明函数只是希望借用,本质上是一个指针。
智能指针
rust 中的默认指针,即引用,需要受到严格的借用检查的约束限制,在一些需要灵活的场景下无法满足业务需求,因此需要使用一些智能指针来管理内存,从而同时获得自由和安全,但是需要一定的开销。
| 智能指针 | 特性 | 所有权共享 | 线程安全性 | 备注 |
|---|---|---|---|---|
| Box<T> Deref | 堆分配 | ❌ | ❌ | 递归结构、大对象 |
| Rc<T> Deref | 共享所有权 | ✅ | ❌ | 单线程共享数据 |
| Arc<T> Deref | 共享所有权 | ✅ | ✅ | 多线程共享数据 |
| Weak<T> Deref | 防止循环引用 | ✅ | ❓ | 适用于树结构 |
| Cell<T> ?Deref | 轻量内部可变 | ❌ | ❌ | 适用于 Copy 类型 |
| RefCell<T> ?Deref | 内部可变性 | ❌ | ❌ | 运行时可变借用 |
| Mutex<T> | 内部可变性 | ❌ | ✅ | 运行时可变借用 |
| RwLock<T> | 内部可变性 | ❌ | ✅ | 运行时可变借用 |
| Atomic<T> | 原子操作 | ❌ | ✅ | 运行时可变借用 |
作用域和生命周期
rust 中有块作用域、函数作用域和全局作用域,作用域会随着程序的执行而展开,同时随着程序的执行结束而关闭。一个变量往往在一个作用域中被声明,此时,我们称这个变量属于这个作用域,当这个作用域被关闭的时候,从属于该作用域的变量将会被清除,rust 将会自动调用这些变量的 drop 方法释放资源。
生命周期泛型
生命周期是对一个引用类型的描述,它表现为一个类型的泛型,
unsafe
unsafe 是 rust 特性子集,通过使用 unsafe 关键可以将一个函数或者 trait 中方法的标记为 unsafe,从而开启 unsafe 特性:
- 解引用裸指针
- 解析 union 的字段
异步编程
在 IO 密集型的程序中,程序的瓶颈出现在程序执行无法避免的 IO 等待,这将导致程序因为进行阻塞 IO 而被操作系统挂起,从而使得程序不能充分占用 CPU,出现了资源利用不充分的情况。
模块系统和包管理
rust 的模块系统有三个主要抽象,package, crate, mod。理解模块是建构大型应用和复用社区生态的前提。