面向对象
使用方法进行封装
Rust 支持对象的概念。“对象”是一个与一些函数(也称为方法)相关联的结构体。
任何方法的第一个参数必须是与方法调用相关联的实例的引用。(例如 instanceOfObj.foo())。Rust 使用:
- &self —— 对实例的不可变引用。
- &mut self —— 对实例的可变引用。
方法是在一个有 impl 关键字的实现块中定义的:
impl MyStruct { ... fn foo(&self) { ... } }
|
下面是一个Rust对象的写法:
struct SeaCreature { noise: String, }
impl SeaCreature { fn get_sound(&self) -> &str { &self.noise } }
fn main() { let creature = SeaCreature { noise: String::from("blub"), }; println!("{}", creature.get_sound()); }
|
抽象与选择性暴露
Rust 可以隐藏对象的内部实现细节。
默认情况下,字段和方法只有它们所属的模块才可访问。
pub 关键字可以将字段和方法暴露给模块外的访问者。
struct SeaCreature { pub name: String, noise: String, }
impl SeaCreature { pub fn get_sound(&self) -> &str { &self.noise } }
fn main() { let creature = SeaCreature { name: String::from("Ferris"), noise: String::from("blub"), }; println!("{}", creature.get_sound()); }
|
使用 Trait 实现多态
Rust 支持多态的特性。Trait 允许我们将一组方法与结构类型关联起来。等于Java的接口
我们首先在 Trait 里面定义函数签名:
trait MyTrait { fn foo(&self); ... }
|
当一个结构体实现一个 trait 时,它便建立了一个契约,允许我们通过 trait 类型与结构体进行间接交互(例如 &dyn MyTrait),而不必知道其真实的类型。结构体实现 Trait 方法是在实现块中定义要实现的方法:
impl MyTrait for MyStruct { fn foo(&self) { ... } ... }
|
下面是复杂代码 :
struct SeaCreature { pub name: String, noise: String, }
impl SeaCreature { pub fn get_sound(&self) -> &str { &self.noise } }
trait NoiseMaker { fn make_noise(&self); }
impl NoiseMaker for SeaCreature { fn make_noise(&self) { println!("{}", &self.get_sound()); } }
fn main() { let creature = SeaCreature { name: String::from("Ferris"), noise: String::from("blub"), }; creature.make_noise(); }
|
Trait 自带方法
Trait 可以有已实现的方法。
这些函数并不能直接访问结构体的内部字段,但它可以在许多 trait 实现者之间共享行为。
struct SeaCreature { pub name: String, noise: String, }
impl SeaCreature { pub fn get_sound(&self) -> &str { &self.noise } }
trait NoiseMaker { fn make_noise(&self); fn make_alot_of_noise(&self){ self.make_noise(); self.make_noise(); self.make_noise(); } }
impl NoiseMaker for SeaCreature { fn make_noise(&self) { println!("{}", &self.get_sound()); } }
fn main() { let creature = SeaCreature { name: String::from("Ferris"), noise: String::from("blub"), }; creature.make_alot_of_noise(); }
|
Traits 可以从其他 trait 继承方法:
struct SeaCreature { pub name: String, noise: String, }
impl SeaCreature { pub fn get_sound(&self) -> &str { &self.noise } }
trait NoiseMaker { fn make_noise(&self); }
trait LoudNoiseMaker: NoiseMaker { fn make_alot_of_noise(&self) { self.make_noise(); self.make_noise(); self.make_noise(); } }
impl NoiseMaker for SeaCreature { fn make_noise(&self) { println!("{}", &self.get_sound()); } }
impl LoudNoiseMaker for SeaCreature {}
fn main() { let creature = SeaCreature { name: String::from("Ferris"), noise: String::from("blub"), }; creature.make_alot_of_noise(); }
|
动态调度和静态调度
方法的执行有两种方式:
- 静态调度——当实例类型已知时,我们直接知道要调用什么函数。
- 动态调度——当实例类型未知时,我们必须想方法来调用正确的函数。
Trait 类型 &dyn MyTrait 给我们提供了使用动态调度间接处理对象实例的能力。
当使用动态调度时,Rust 会鼓励你在你的 trait 类型前加上dyn,以便其他人知道你在做什么。
内存细节:
- 动态调度的速度稍慢,因为要追寻指针以找到真正的函数调用。
struct SeaCreature { pub name: String, noise: String, }
impl SeaCreature { pub fn get_sound(&self) -> &str { &self.noise } }
trait NoiseMaker { fn make_noise(&self); }
impl NoiseMaker for SeaCreature { fn make_noise(&self) { println!("{}", &self.get_sound()); } }
fn static_make_noise(creature: &SeaCreature) { // 我们知道真实类型 creature.make_noise(); }
fn dynamic_make_noise(noise_maker: &dyn NoiseMaker) { // 我们不知道真实类型 noise_maker.make_noise(); }
fn main() { let creature = SeaCreature { name: String::from("Ferris"), noise: String::from("咕噜"), }; static_make_noise(&creature); dynamic_make_noise(&creature); }
|
Trait 对象
当我们将一个对象的实例传递给类型为 &dyn MyTrait 的参数时,我们传递的是所谓的 trait 对象。
Trait 对象允许我们间接调用一个实例的正确方法。一个 trait 对象对应一个结构。 它保存着我们实例的指针,并保有一个指向我们实例方法的函数指针列表。
内存细节:
处理未知大小的数据
当我们想把 Trait 存储在另一个结构struct中时,它们亦带来了一个有趣的挑战。 Trait 混淆了原始结构,因此它也混淆了原来的结构体的大小。在 Rust 中,在结构体中存储未知大小的值有两种处理方式。
- 泛型(generics)——使用参数化类型创建已知类型的结构/函数,因此大小变成已知的。
- 间接存储(indirection)——将实例放在堆上,给我们提供了一个间接的层次,让我们不必担心实际类型的大小,只需存储一个指向它的指针。不过还有其他方法!