如何访问动态结构体的字段?

4

最近我一直在学习高级的Rust技术。其中,我正在学习使用动态分发。

在我的尝试过程中,我遇到了一个问题。由于某种原因,我似乎无法访问使用Boxes和动态分发赋值给变量的结构体的字段。例如:

fn main() {
    let z: Box<dyn S>;
    z = Box::new(A::new());

    println!("{}", z.val);
}

trait S {
    fn new () -> Self where Self: Sized;
}


struct A {
    val: i32,
}

impl S for A {
    fn new () -> A {
        A {val: 1}
    }
}

struct B {
    val: i32
}

impl S for B {
    fn new() -> B {
        B {val:2}
    }
}

产生错误消息"error[E0609]: no field <code>val</code> on type <code>Box<dyn S></code>"

有没有办法访问这样的字段,或者我需要拼凑出一个解决方法?


1
没有办法做到这一点。也没有什么把戏可以欺骗它。向下转型一直是一个恶心的黑客和错误的做事方式,我为 Rust 摆脱了这个噩梦而喝彩。Trait 对象是一种你可以对其执行非常少量有限操作的东西,仅此而已 - Silvio Mayolo
@SilvioMayolo在这种情况下,您建议我使用什么代替动态分派? - The Daleks stand with Ukraine
@SilvioMayolo 我目前正在开发超级星际远航的 Rust 移植版。我希望有一个 Ship trait,它将被实现为代表 Enterprise 和 Faerie Queen(备用船)的结构体。这样,如果玩家不得不放弃 Enterprise,我就不需要太多额外的逻辑,只需使用 Faerie Queen 的结构体作为替代即可。 - The Daleks stand with Ukraine
2
如果你只有两种可能性,那么使用 enum 是一个很好的用例。enum 类似于 trait,但是实现者数量是有限的,并且你打算基于它们进行区分。 - Silvio Mayolo
1
请注意,从严格的面向对象的角度来看,您不应该从对象的方法之外访问字段(直接或通过get/set访问器):为什么getter和setter方法是有害的 - Jmb
显示剩余2条评论
1个回答

5
如果您理解什么是 trait object,就很容易理解为什么这样做行不通了。当一个方法返回 dyn Trait 时,它并不返回任何 struct 的实例,而是返回了一个查找表,告诉调用者在哪里找到它的方法。因此,调用者可以访问方法,而不需要知道底层的 struct 是什么。

所以,如果调用者没有访问该 struct 的权限,显然就无法访问其字段。

有两种实现你想要做的事情的方法:

  1. 使用 trait 的方法来访问所需字段。如果 trait 可能的实现者列表未预先确定,并且 trait 本身不太复杂(作为 trait 对象表示的 trait 有一些限制),这是一个好选择。请注意,trait 对象会带来一些运行时开销(使用查找表比直接方法访问更慢)。
  2. 使用 enum。如果您知道方法可能返回的选项的完整列表,则这是最佳的 Rust'y 解决方案。
enum S {
  A { val: i32, anotherAval: u32 },
  B { val: i32, anotherBval: f32 },
}

impl S {
  fn val(&self) -> i32 {
    match self {
      S::A => A.val,
      S::B => B.val,
    }
  }
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接