Rust - 相同类型的不同行为

Rust - Same Type different behaviour

就我的理解而言,泛型允许在不同类型之间共享相同的行为。例如,

trait Bird {}

struct BirdFly {}

impl Bird for BirdFly  {
   pub fn fly() -> can fly
}

struct BirdCantFly {}

impl Bird for BirdCantFly{
   pub fn fly() -> can't fly
}

let birds = vec![
    Box::new(BirdFly{}),        // allow this bird to fly, for instance
    Box::new(BirdCantFly{}),    // don't allow this bird to fly
];

我的问题是相反的,即是否可以让相同的类型采取不同的行为(没有 ifs、enums 或 Box)。在这个例子中,拥有一个 Box 似乎是一种浪费, BirdFly 和 BirdCantFly 在两种类型(BirdFly 和 BirdCantFly)中尺寸相同,只有行为不同。

类似于:

struct Bird {
   fly: // associate different behavior
}

let birds = vec![
    Bird{ fly: some_fly_behavior },      // allow this bird to fly, for instance
    Bird{ fly: another_fly_behavior },   // don't allow this bird to fly
];

birds[0].fly();
birds[1].fly();

如果 fly 收到相同的参数和 returns 相同的类型,我看不出问题的原因。此外,通过这种方式我可以摆脱向量中的 Box。特别是因为我可能在向量中有数百万个元素并且被多次迭代访问,这样我就可以避免开销。

感谢您的帮助!

您可以将 function pointer 存储在 struct Bird 中并添加一个辅助方法来获取您想要的语法。

struct Bird {
    name: String,
    fly_impl: fn(&Bird) -> (),
}

impl Bird {
    fn new(name: String, fly_impl: fn(&Bird) -> ()) -> Bird {
        Bird { name, fly_impl }
    }

    fn fly(self: &Bird) {
        (self.fly_impl)(self)
    }
}

fn behavior1(b: &Bird) {
    println!("{} behavior1", b.name);
}

fn behavior2(b: &Bird) {
    println!("{} behavior2", b.name);
}

fn main() {
    let captured_variable = 10;
    let birds = vec![
        Bird::new("Bird1".into(), behavior1),
        Bird::new("Bird2".into(), behavior2),
        Bird::new("Bird3".into(), |b| println!("{} lambda", b.name)),
        /*Bird::new("Bird4".into(), |b| {
            println!("{} lambda with {}", b.name, captured_variable)
        }),*/
    ];
    (birds[0].fly_impl)(&birds[0]);
    for bird in birds {
        bird.fly();
    }
}

fn 不要与 trait Fn 混淆。前者仅允许函数和非捕获 lambda,并且具有固定大小。后者允许任意大小的任意捕获,因此需要像 Box 这样的间接寻址。两者都在调用时进行动态调度。

请注意 Bird3 的行为是如何由非捕获 lambda 指定的,这是允许的。 但是,如果您尝试取消注释 Bird4,该示例将不会编译,因为它所需的行为是捕获 lambda,它通常可以增长任意大,我们已经禁止 Boxes 和其他间接寻址.

我对 Rust 的了解还不够多,无法告诉您是否有更像 Rust 的解决方案。但是,你的情况很具体:

  • 您有一个任意可扩展的 Bird 层次结构。否则 enum 更合适。
  • 但是,此层次结构中的所有 Bird 都具有完全相同的字段集和受支持的 trait。否则,您必须通过 Box<dyn Bird>.
  • 使用 traits 和标准动态调度
  • 您不想为单个 Bird 分配堆。否则,您可以使用 traits 和标准动态调度。