Rust圣经(二)

12 次浏览
2024年07月30日创建

注意 dyn 不能单独作为特征对象的定义,例如下面的代码编译器会报错,原因是特征对象可以是任意实现了某个特征的类型,编译器在编译期不知道该类型的大小,不同的类型大小是不同的。

而 &dyn 和 Box<dyn> 在编译期都是已知大小,所以可以用作特征对象的定义。

特征对象大小不固定:这是因为,对于特征 Draw,类型 Button 可以实现特征 Draw,类型 SelectBox 也可以实现特征 Draw,因此特征没有固定大小

几乎总是使用特征对象的引用方式,如 &dyn Draw、Box<dyn Draw>

虽然特征对象没有固定大小,但它的引用类型的大小是固定的,它由两个指针组成(ptr 和 vptr),因此占用两个指针大小

一个指针 ptr 指向实现了特征 Draw 的具体类型的实例,也就是当作特征 Draw 来用的类型的实例,比如类型 Button 的实例、类型 SelectBox 的实例

另一个指针 vptr 指向一个虚表 vtable,vtable 中保存了类型 Button 或类型 SelectBox 的实例对于可以调用的实现于特征 Draw 的方法。当调用方法时,直接从 vtable 中找到方法并调用。之所以要使用一个 vtable 来保存各实例的方法,是因为实现了特征 Draw 的类型有多种,这些类型拥有的方法各不相同,当将这些类型的实例都当作特征 Draw 来使用时(此时,它们全都看作是特征 Draw 类型的实例),有必要区分这些实例各自有哪些方法可调用.

在 Rust 中,有两个self,一个指代当前的实例对象,一个指代特征或者方法类型的别名.

不是所有特征都能拥有特征对象,只有对象安全的特征才行。当一个特征的所有方法都有如下属性时,它的对象才是安全的:

方法的返回类型不能是 Self

方法没有任何泛型参数

对象安全对于特征对象是必须的,因为一旦有了特征对象,就不再需要知道实现该特征的具体类型是什么了。如果特征方法返回了具体的 Self 类型,但是特征对象忘记了其真正的类型,那这个 Self 就非常尴尬,因为没人知道它是谁了。但是对于泛型类型参数来说,当使用特征时其会放入具体的类型参数:此具体类型变成了实现该特征的类型的一部分。而当使用特征对象时其具体类型被抹去了,故而无从得知放入泛型参数类型到底是什么。

struct Millimeters(u32);
struct Meters(u32);

impl Add<Meters> for Millimeters {
    type Output = Millimeters;

    fn add(self, rhs: Meters) -> Self::Output {
        Millimeters(self.0 + (rhs.0 * 1000))
    }
}

这里,是进行 Millimeters + Meters 两种数据类型的 + 操作,因此此时不能再使用默认的 RHS,否则就会变成 Millimeters + Millimeters 的形式。使用 Add<Meters> 可以将 RHS 指定为 Meters,那么 fn add(self, rhs: RHS) 自然而言的变成了 Millimeters 和 Meters 的相加。

trait Pilot {
    fn fly(&self);
}

trait Wizard {
    fn fly(&self);
}

struct Human;

impl Pilot for Human {
    fn fly(&self) {
        println!("This is your captain speaking.");
    }
}

impl Wizard for Human {
    fn fly(&self) {
        println!("Up!");
    }
}

impl Human {
    fn fly(&self) {
        println!("*waving arms furiously*");
    }
}
fn main() {
    let person = Human;
    Pilot::fly(&person); // 调用Pilot特征上的方法
    Wizard::fly(&person); // 调用Wizard特征上的方法
    person.fly(); // 调用Human类型自身的方法
}

指定

fn main() {
    println!("A baby dog is called a {}", <Dog as Animal>::baby_name());
}

unwrap_or

为什么不在结构体中使用字符串字面量或者字符串切片,而是统一使用 String 类型?原因很简单,后者在结构体初始化时,只要转移所有权即可,而前者,抱歉,它们是引用,它们不能为所欲为。

函数或者方法中,参数的生命周期被称为 输入生命周期,返回值的生命周期被称为 输出生命周期.

在 Rust 中,生命周期参数的使用和约束是非常重要且细致的。你提到的两个代码片段展示了不同的生命周期参数约束方式。让我们深入分析为什么第一个片段可以通过编译,而第二个片段不能。

### 第一个片段

```rust

impl<'a: 'b, 'b> ImportantExcerpt<'a> {

fn announce_and_return_part(&'a self, announcement: &'b str) -> &'b str {

println!("Attention please: {}", announcement);

self.part

}

}

```

在这个实现中:

- `impl<'a: 'b, 'b> ImportantExcerpt<'a>`:这个声明表示 `'a` 生命周期必须至少和 `'b` 一样长,或者更长。

- `fn announce_and_return_part(&'a self, announcement: &'b str) -> &'b str`:这个方法接受一个生命周期为 `'a` 的引用 `&self`,以及一个生命周期为 `'b` 的字符串切片 `announcement`。返回值的生命周期也是 `'b`。

这个实现能够通过编译的原因是:

1. 生命周期 `'a` 被约束为至少和 `'b` 一样长(`'a: 'b`),这意味着 `self.part` 的生命周期 `'a` 可以安全地缩短到 `'b`。

2. 返回值的生命周期 `'b` 可以安全地绑定到 `announcement` 的生命周期。

### 第二个片段

```rust

impl<'a> ImportantExcerpt<'a> {

fn announce_and_return_part<'b>(&'a self, announcement: &'b str) -> &'b str {

println!("Attention please: {}", announcement);

self.part

}

}

```

在这个实现中:

- `impl<'a> ImportantExcerpt<'a>`:这个声明没有对 `'a` 和 `'b` 之间的关系做任何约束。

- `fn announce_and_return_part<'b>(&'a self, announcement: &'b str) -> &'b str`:这个方法接受一个生命周期为 `'a` 的引用 `&self`,以及一个生命周期为 `'b` 的字符串切片 `announcement`。返回值的生命周期也是 `'b`。

这个实现不能通过编译的原因是:

1. Rust 编译器无法推断 `self.part` 的生命周期 `'a` 如何与返回值的生命周期 `'b` 关联。

2. `self.part` 的生命周期 `'a` 不能直接缩短到 `'b`,因为没有显式的约束来表明 `'a` 至少和 `'b` 一样长。

3. 编译器会认为 `self.part` 的生命周期可能比 `announcement` 的生命周期更长,从而导致潜在的悬垂引用问题。

### 解决方法

为了让第二个片段通过编译,可以显式地表明返回值的生命周期和 `self.part` 的生命周期一致,而不是 `announcement` 的生命周期:

```rust

impl<'a> ImportantExcerpt<'a> {

fn announce_and_return_part(&'a self, announcement: &str) -> &'a str {

println!("Attention please: {}", announcement);

self.part

}

}

```

在这个修正的实现中:

- `fn announce_and_return_part(&'a self, announcement: &str) -> &'a str`:返回值的生命周期是 `'a`,与 `self.part` 的生命周期一致。

这样可以确保返回的引用 `self.part` 的生命周期与 `self` 一致,而不依赖于 `announcement` 的生命周期。