【问题标题】:Storing trait objects in structs将特征对象存储在结构中
【发布时间】:2016-02-05 20:45:21
【问题描述】:

I'm working on a parser 有一堆不同的节点(现在约 6 种节点类型,稍后会更多),我有点迷失如何与节点项交互(所有节点都实现 Node 特征)所以我需要在任何地方使用Box<Node>

ListNode 看起来像:

pub struct ListNode {
    kind: NodeKind,
    position: usize,
    pub nodes: Vec<Box<Node>>
}

但我无法派生Clone,因为Node 没有实现它,并且每当我尝试获取例如测试中的那种节点时:

#[test]
fn test_plain_string() {
    let mut parser = Parser::new("plain_string", "Hello world");
    parser.parse();
    assert_eq!(1, parser.root.nodes.len());
    let ref node = parser.root.nodes[0];
    let kind = node.get_kind();
    assert_eq!(kind, NodeKind::Text);
}

我会遇到类似这样的借用和调整大小错误:

src/parser.rs:186:20: 186:24 error: cannot move out of borrowed content [E0507]
src/parser.rs:186         let kind = node.get_kind();
                                     ^~~~
src/parser.rs:186:20: 186:24 error: cannot move a value of type nodes::Node + 'static: the size of nodes::Node + 'static cannot be statically determined [E0161]
src/parser.rs:186         let kind = node.get_kind();

类似于这个测试is in the tests

我应该如何访问 trait 对象或 Rust 中存在缺陷的这种方法? 是否可以将特征实现到特征(如 Debug)到特征,或者我是否为每个嵌入 Node 的结构手动实现 Debug

【问题讨论】:

  • Node trait 的替代方案是使用枚举变体。这将解决尺寸问题。像这样:is.gd/IFSAi6

标签: rust


【解决方案1】:

错误来自Node 上的get_kind 方法按值预期self,而不是&amp;self。通过值传递self 意味着该方法获得了对象的所有权(因此丢弃它并结束方法),这在此处不是必需的。一般来说,你应该默认使用&amp;self,然后如果你需要改变对象,则更改为&amp;mut self,或者如果你需要使用对象(例如,因为你需要将对象的字段之一移动到其他地方)而且你不想克隆它)。

顺便说一句,我注意到你为你的结构实现了ToString。但是,ToString 的文档说:

这个 trait 会自动为任何实现了的类型实现 Display 特征。因此,不应实施ToString 直接:Display 应该改为实现,你会得到 ToString 免费实施。


除了使用 trait,您还可以考虑使用 enum,尤其是在节点类型事先已知且您不需要可扩展性的情况下。此外,如果稍后您需要确定节点的类型,使用enum 会更自然(只需进行模式匹配)。

看来您需要所有节点类型都具有kindposition 属性。根据您将如何使用这些节点,您可能会发现使用这些字段定义一个结构以及为特定类型字段定义一个枚举会更有用。

struct Node {
    //kind: NodeKind, // redundant!
    position: usize,
    specific: SpecificNode,
}

enum SpecificNode {
    List(Vec<Box<Node>>),
    Text(String),
    VariableBlock(Box<Node>),
    Identifier(String),
    Int(i32),
    Float(f32),
    Bool(bool),
}

嗯,SpecificNode 枚举看起来很像您的 NodeKind 枚举,不是吗?事实上,kind 字段变得多余,因为您可以通过查看specific 字段的变体来确定种类。

现在,无需在每种类型的节点上分别实现方法,您只需定义一次,尽管每个方法通常需要模式匹配 self 才能访问任何变体的数据。

如果您有仅适用于一个特定变体的方法(例如,仅对 List 有意义的方法),使用 enum 方法,您将能够在任何类型的节点上调用它,因为每个 enum 变体没有不同的类型(从 Rust 1.6 开始;有关于引入此功能的讨论)。但是,您的代码大部分时间都可能在抽象 Node 上运行,并且无论如何您都必须在调用该方法之前验证您拥有什么样的节点,因此您不妨将该逻辑直接移到这些方法中.

【讨论】:

  • 完美,没想到 Rust 枚举这么强大
猜你喜欢
  • 2015-08-01
  • 2021-04-21
  • 2010-10-01
  • 1970-01-01
  • 1970-01-01
  • 2023-03-18
  • 1970-01-01
  • 2020-07-14
  • 2020-02-29
相关资源
最近更新 更多