• Rust中dyn和impl关键字的用法
  • 发布于 2个月前
  • 207 热度
    0 评论
让我们来谈谈dyn和impl关键字以及'static生命周期!dyn关键字用于表示实现给定某种类型trait的对象。这就是多态性的本质——我们不需要在编译时知道具体的类型;我们只需要知道这个类型实现了这个trait。当我们调用与trait关联的方法时,它将被动态分派。由于具体对象是未知的,编译器不能直接传递它,因为它的大小是未知的。相反,它需要传递对象的指针或引用。这就是为什么dyn关键字通常与智能指针一起使用,如Box<dyn Trait>。

另一方面,impl关键字表示具体类型在编译时是已知的。这本质上是一个泛型类型,相当于C++中的模板。编译器确切地知道它是哪种类型,并可以直接传递对象。如果我们调用任何与trait相关的方法,它将被静态分派。然而,由于具体类型是固定的,我们不能在运行时用不同的对象去替换对象。

思考下面的代码:
// 堆代码 duidaima.com
trait Weapon {
    fn fire(&self);
}

struct M16Rifle;
impl Weapon for M16Rifle {
    fn fire(&self) {
        println!("dut dut");
    }
}

struct Bazuka;
impl Weapon for Bazuka {
    fn fire(&self) {
        println!("Kablam!!");
    }
}

struct Player {
    weapon: Box<dyn Weapon>,
}

impl Player {
    fn new(weapon: impl Weapon) -> Self {
        Self { weapon: Box::new(weapon) }
    }

    fn change_weapon(&mut self, weapon: impl Weapon) {
        self.weapon = Box::new(weapon);
    }

    fn shoot(&self) {
        self.weapon.fire();
    }
}

fn main() {
    let mut player = Player::new(M16Rifle);
    player.shoot();
    player.change_weapon(Bazuka);
    player.shoot();
}
玩家对象持有一些使用Box<dyn Weapon>实现的武器对象。玩家的武器可以在运行时切换到不同的类型,例如,从M16Rifle切换到Bazuka。代码看起来很好,但是Rust编译器给出了一个错误:
error[E0310]: the parameter type `impl Weapon` may not live long enough
  --> src/main.rs:25:24
   |
25 |         Self { weapon: Box::new(weapon) }
   |                        ^^^^^^^^^^^^^^^^ ...so that the type `impl Weapon` will meet its required lifetime bounds
   |
help: consider adding an explicit lifetime bound...
   |
24 |     fn new(weapon: impl Weapon + 'static) -> Self {
   |                                +++++++++
为什么编译器要求添加'static生命周期说明符?这可能看起来很可怕,因为你不希望你的武器对象有一个“静态生命周期”。静态生命周期对象占用的内存直到程序结束时才会被释放,这可能会导致内存泄漏。实际上,编译器不是这么说的。按照编译器的建议指定'static生命周期不会使武器突然变成静态分配的。

代码修改如下:
impl Player {
    fn new(weapon: impl Weapon + 'static) -> Self {
        Self { weapon: Box::new(weapon) }
    }

    fn change_weapon(&mut self, weapon: impl Weapon + 'static) {
        self.weapon = Box::new(weapon);
    }
    ......
}
相反,在方法签名中添加'static说明符是表示武器对象类型必须满足以下要求之一:
1,它没有与生命周期相关的变量。
2,它有一个与生命周期相关联的变量,但这个生命周期是'static。

在我们的例子中,M16Rifle和Bazuka结构体都没有任何与生命周期相关的变量,因此它们满足要求1,这些对象在超出作用域时将被正常丢弃。事实上,当你写Box<dyn Weapon>时,它隐式地添加了'static说明符 - Box<dyn Weapon 'static>。

那么,要求2什么时候起作用呢?让我们在游戏中添加第三种武器:
impl<'a> CustomWeapon<'a> {
    fn new(sound: &'a str) -> Self {
        Self { sound }
    }
}

impl<'a> Weapon for CustomWeapon<'a> {
    fn fire(&self) {
        println!("{}", self.sound)
    }
}

fn main() {
    ......
    player.change_weapon(CustomWeapon::new("pew pew"));
    player.shoot();
}
我们的第三个武器:CustomWeapon<'a>,有一个生命周期说明符'a,因为它有一个变量声音,是一个str的引用。当我们用CustomWeapon::new("pew pew")创建这个武器时,我们提供的字面值字符串有一个'static生命周期。因此,这个对象满足要求2,并且编译得很好。

总之,在Rust中使用dyn和impl关键字可以实现灵活高效的代码设计。dyn关键字允许通过trait实现对不同具体类型的对象进行统一处理,从而实现多态性。另一方面,impl关键字在编译时提供静态分派和精确的类型信息,允许直接对对象进行操作。理解生命周期在这些场景中的作用对于确保正确的代码行为至关重要。虽然添加'static生命周期说明符似乎令人生畏,但它并不意味着静态分配,而是表示对对象的生命周期有特定要求。通过有效地利用这些关键字和生命周期,Rust程序员可以构建强大且可维护的代码库。
用户评论