Rust 语言学习之旅(4)


面向对象
使用方法进行封装
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 对象对应一个结构。 它保存着我们实例的指针,并保有一个指向我们实例方法的函数指针列表。
内存细节:

  • 这个函数列表在 C++ 中被称为 vtable。

处理未知大小的数据
当我们想把 Trait 存储在另一个结构struct中时,它们亦带来了一个有趣的挑战。 Trait 混淆了原始结构,因此它也混淆了原来的结构体的大小。在 Rust 中,在结构体中存储未知大小的值有两种处理方式。

  • 泛型(generics)——使用参数化类型创建已知类型的结构/函数,因此大小变成已知的。
  • 间接存储(indirection)——将实例放在堆上,给我们提供了一个间接的层次,让我们不必担心实际类型的大小,只需存储一个指向它的指针。不过还有其他方法!