Generic
- 泛型: 是具体类型或其他属性的抽象代替,能够提高代码的复用能力
- Rust 中使用泛型的代码和使用具体类型的代码运行速度是一样的
- Rust 在编译过程中会做单态化 (monomorphization) 处理 (编译时将泛型替换为具体类型的过程), 泛型函数里每个用到的类型会编译出一份代码
1 2 |
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 |
|
1 2 3 4 5 |
|
Trait
- Trait: 抽象的定义共享行为
- 告诉编译器,某种类型具有哪些并且可以与其他类型共享的功能
- Trait bounds(约束): 泛型类型参数指定为特定行为的类型
- 可以在某个类型上实现某个 Trait 的前提条件是:
- 这个类型或这个 trait 是在本地 crate 里定义的
- 无法为外部类型来实现外部的 trait (孤儿原则: 确保其他人的代码不能破坏您的代码,反之亦然)
- Rust 的面相对象特性
- 封装: 私有字段和 pub 关键字
- 继承: Rust 中使用 trait 方法来进行代码共享
- trait 默认方法,可使实现改 trait 的对象复用逻辑
- 而实现 trait 的对象也可以重写这些默认的方法
- 多态: 使用泛型和 trait 约束 (限定参数化多态 bounded parametric)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
|
1 2 3 4 5 6 7 8 9 10 11 12 13 |
|
使用 trait bound 有条件的实现方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
|
1 2 3 4 5 6 7 8 |
|
默认实现
- 默认实现的方法,可以调用 trait 中的其他方法,即使这些方法没有默认实现
- 无法在方法的重写实现里面调用默认的实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
|
使用 trait 对象存储不同类型的值
- 需求: 创建一个 GUI 工具:
- 它会遍历某个元素的列表,依次调用元素的 draw 方法进行绘制
- 例如: Button, TextField 等元素
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 |
|
Trait 对象执行的是动态派发
- 将 trait 约束作用于泛型时,Rust 编译器会执行单态化
- 编译器会为我们用来替换泛型类型参数的每一个具体类型生成对应函数和方法的非泛型实现
- 通过单态化生成的代码会执行静态派发(static dispatch),在编译过程中确定调用的具体方法
- 动态派发(dynamic dispatch)
dyn 关键字
- 无法在编译过程中确定你调用的究竟是哪一种方法
- 编译器会产生额外的代码以便在运行时找出希望调用的方法
- 使用 trait 对象, 会执行动态派发
- 会产生运行时开销
- 会阻止编译器内联方法代码,使得部分优化操作无法进行
Trait 对象必须保证对象安全
- 只能把满足对象安全 (object-safe) 的 trait 转化为 trait 对象
- Rust 采用一系列规则来判定某个对象是否安全,只需要记住两条:
- 方法的返回类型不是 Self
- 方法中不包含任何泛型类型参数
1 2 3 4 5 6 7 8 |
|
在 trait 定义中使用关联类型来指定占位类型
- 关联类型 (associated type) 是 Trait 中的类型占位符, 它可以用于 trait 的方法签名中
- 可以定义出包含某些类型的 trait, 而在实现前无需知道这些类型是什么
1 2 3 4 5 |
|
关联类型和泛型的区别
泛型 | 关联类型 |
---|---|
每次实现 trait 时需要标注类型 | 无需标注类型 |
可以为一个类型多次实现某个 trait (不同的泛型参数) | 无法为单个类型多次实现某个 trait |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
|
默认泛型参数和运算符重载
- 可以在使用泛型参数时为泛型指定一个默认的具体类型
<PlaceholderType=ConcreteType>
- 这种技术常用于运算符重载 (operator overloading)
- Rust 不允许创建自己的运算符及重载任意的运算符, 但可以通过实现
std::ops
中列出的那些 trait 来重载一部分相应的运算符
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
|
1 2 3 4 5 6 7 8 9 10 11 12 13 |
|
完全限定语法
- 完全限定语法
<Type as Trait>::function(receiver_if_method, next_arg, ...)
- 可以在任何调用函数或方法的地方使用
- 允许忽略那些从其他上下文能推导出来的部分
- 当 Rust 无法区分你期望调用哪个具体实现的时候, 才需使用这种语法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
|
使用 supertrait 来要求 trait 附带其他 trait 的功能
- 需要在一个 trait 中使用其他 trait 的功能
- 需要被依赖的 trait 使用其他 trait 的功能
- 那个被间接依赖的 trait 就是当前 trait 的 supertrait
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
|
newtype 模式
- newtype 模式
- 可以用来静态的保证各种值之间不会混淆并表明值的单位
- 可以为类型的某些细节提供抽象能力
- 通过轻量级的封装来隐藏内部的实现细节
外部类型上实现外部 trait
- 孤儿规则: 只有当 trait 或类型定义在本地包时, 才能为该类型实现这个 trait
- 可以使用 newtype 模式来绕过这一规则
- 利用 tuple struct 创建一个本地的新类型
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
|