用于 AST 操作的 Rc 中的向下转换特征 [英] Downcast traits inside Rc for AST manipulation

查看:25
本文介绍了用于 AST 操作的 Rc 中的向下转换特征的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在尝试在 Rust 中操作 AST.会有很多操作,我希望我的树是不可变的,所以为了节省时间,所有引用都将是 Rcs.

I'm trying to manipulate ASTs in Rust. There will be lots of manipulations, and I want my trees to be immutable, so to save time all references will be Rcs.

我的树节点将如下所示:

My tree nodes will look like this:

enum Condition {
    Equals(Rc<Expression>, Rc<Expression>),
    LessThan(Rc<Expression>, Rc<Expression>),
    ...
}

enum Expression {
    Plus(Rc<Expression>, Rc<Expression>),
    ...
}

我想用另一个相同类型的节点替换给定类型的随机节点.为了对树进行通用操作,我做了一个特征:

I want to replace a random node of a given type with another node of the same type. To do generic operations on trees I've made a trait:

trait AstNode {
    fn children(&self) -> Vec<Rc<AstNode>>;
}

并且所有节点都实现了这一点.这使我可以遍历树,而无需为每个操作解构每个节点类型,只需调用 children().

And all nodes implement this. This allows me to walk the tree without having to destructure each node type for every operation, by simply calling children().

我还想克隆一个节点,同时只更新它的一个子节点,而将其他子节点留在原地.假设我已经能够生成正确的具体类型的节点(如果我错了,我很高兴程序会恐慌).我将在 trait 中添加以下方法:

I also want to clone a node while updating only one of its children, and leaving the other ones in place. Assume that I've been able to generate nodes of the right concrete type (and I'm happy for the program to panic if I'm wrong). I'll add the following method to the trait:

trait AstNode {
    fn clone_with_children(&self, new_children: Vec<Rc<AstNode>>) -> Self
        where Self: Sized;
}

我的计划是取childen()返回的children,替换其中一个,然后调用clone_with_children()来构造一个相同enum变体的节点,但是替换了一个节点.

My plan is to take the children returned by childen(), replace one of them, and call clone_with_children() to construct a node of the same enum variant but with one node replaced.

我的问题是如何编写clone_with_children().

我需要将 Rc 向下转换为 Rc(或者你有什么),同时将引用计数保留在 Rc 相同,但我发现的所有向下转型的库似乎都无法做到这一点.

I need to downcast Rc<AstNode> to Rc<Expression> (or what have you), while keeping the refcount inside the Rc the same, but none of the downcasting libraries I've found seem to be able to do that.

我想要的是可能的,还是应该完全不同?

Is what I want possible, or should I do it completely differently?

推荐答案

注意:在这个答案中,当类型是特征对象时,我将使用 dyn Trait 语法使其更加清晰.编写 Rc 的旧方法是 Rc.请参阅 dyn"有什么作用?是指类型吗?

Note: In this answer I will use the dyn Trait syntax to make it more clear when a type is a trait object. The older way to write Rc<dyn Trait> is Rc<Trait>. See What does "dyn" mean in a type?

不,你不能将 Rc 向下转换为 Rc,因为像 dyn Trait 这样的 trait 对象不t 包含有关数据所属的具体类型的任何信息.

No, you can't downcast Rc<dyn Trait> to Rc<Concrete>, because trait objects like dyn Trait don't contain any information about the concrete type the data belongs to.

以下是适用的官方文档的摘录指向所有指向 trait 对象的指针(&dyn TraitBoxRc 等):

Here's an excerpt from the official documentation that applies to all pointers to trait objects (&dyn Trait, Box<dyn Trait>, Rc<dyn Trait>, etc.):

pub struct TraitObject {
    pub data: *mut (),
    pub vtable: *mut (),
}

data 字段指向结构本身,vtable 字段指向一组函数指针,每个函数指针对应一个 trait.在运行时,这就是你所拥有的.这不足以重建结构的类型.(使用 Rcdata 指向的块也包含强引用和弱引用计数,但没有额外的类型信息.)

The data field points to the struct itself, and the vtable field points to a collection of function pointers, one for each method of the trait. At runtime, that's all you have. And that's not sufficient to reconstruct the struct's type. (With Rc<dyn Trait>, the block data points to also contains the strong and weak reference counts, but no additional type information.)

但至少还有 3 个其他选择.

But there are at least 3 other options.

首先,你可以把你需要在Expressions或Conditions上做的所有操作添加到traitAstNode中,并实现它们用于每个结构.这样你就永远不需要调用一个在 trait 对象上不可用的方法,因为 trait 包含了你需要的所有方法.

First, you could add all the operations that you need to do on Expressions or Conditions to the trait AstNode, and implement them for each struct. This way you never need to call a method that isn't available on the trait object, because the trait contains all the methods you need.

这可能需要用 Rc 替换树中的大多数 RcRc 成员,因为你不能向下转换 Rc(但请参阅下面关于 Any 的内容):

This likely entails replacing most Rc<Expression> and Rc<Condition> members in the tree with Rc<dyn AstNode>, since you can't downcast Rc<dyn AstNode> (but see below about Any):

enum Condition {
    Equals(Rc<dyn AstNode>, Rc<dyn AstNode>),
    LessThan(Rc<dyn AstNode>, Rc<dyn AstNode>),
    ...
}

对此的一种变体可能是在 AstNode 上编写接受 &self 并返回对各种具体类型的引用的方法:

A variation on this might be writing methods on AstNode that take &self and return references to various concrete types:

trait AstNode {
    fn as_expression(&self) -> Option<&Expression> { None }
    fn as_condition(&self) -> Option<&Condition> { None }
    ...
}

impl AstNode for Expression {
    fn as_expression(&self) -> Option<&Expression> { Some(self) }
}

impl AstNode for Condition {
    fn as_condition(&self) -> Option<&Condition> { Some(self) }
}

与其将 Rc 向下转换为 Rc,不如将其存储为 AstNode 并调用例如rc.as_condition().unwrap().method_on_condition(),如果你确信 rc 实际上是一个 Rc.

Instead of downcasting Rc<dyn AstNode> to Rc<Condition>, just store it as an AstNode and call e.g. rc.as_condition().unwrap().method_on_condition(), if you're confident rc is in fact an Rc<Condition>.

其次,您可以创建另一个统一 ConditionExpression 的枚举,并完全取消 trait 对象.这就是我在自己的 Scheme 解释器的 AST 中所做的.使用此解决方案,不需要向下转换,因为所有类型信息都在 enum 变体中.(同样使用此解决方案,如果您需要获取 RcRc,您肯定必须替换 Rc> 算了.)

Second, you could create another enum that unifies Condition and Expression, and do away with trait objects entirely. This is what I have done in the AST of my own Scheme interpreter. With this solution, no downcasting is required because all the type information is in the enum variant. (Also with this solution, you definitely have to replace Rc<Condition> or Rc<Expression> if you need to get an Rc<Node> out of it.)

enum Node {
    Condition(Condition),
    Expression(Expression),
    // you may add more here
}
impl Node {
    fn children(&self) -> Vec<Rc<Node>> { ... }
}

使用Any

第三种选择是使用 AnyRc::downcast 根据需要将每个 Rc 转换为它的具体类型.

Downcast with Any

A third option is to use Any, and Rc::downcast each Rc<dyn Any> into its concrete type as needed.

对此的一个细微变化是添加一个方法 fn as_any(&self) ->&Any { self }AstNode,然后你可以通过编写调用 Expression 方法(需要 &self)node.as_any().downcast_ref::().method_on_expression().但目前没有办法(安全地)向上投射RcRc(不过这在未来可能会改变).

A slight variation on that would be to add a method fn as_any(&self) -> &Any { self } to AstNode, and then you can call Expression methods (that take &self) by writing node.as_any().downcast_ref::<Expression>().method_on_expression(). But there is currently no way to (safely) upcast an Rc<dyn Trait> to an Rc<dyn Any> (this could change in the future, though).

Any 严格来说,是最接近问题答案的东西.我不推荐它,因为低头,或需要低头,通常表明设计很糟糕.即使在具有类继承的语言中,比如 Java,如果你想做同样的事情(例如,在 ArrayList 中存储一堆节点),你必须要么使所有需要的操作在基类或某处可用,枚举您可能需要向下转换的所有子类,这是一个可怕的反模式.您在此处使用 Any 执行的任何操作在复杂性上都与将 AstNode 更改为枚举相当.

Any is, strictly speaking, the closest thing to an answer to your question. I don't recommend it because downcasting, or needing to downcast, is often an indication of a poor design. Even in languages with class inheritance, like Java, if you want to do the same kind of thing (store a bunch of nodes in an ArrayList<Node>, for example), you'd have to either make all needed operations available on the base class or somewhere enumerate all the subclasses that you might need to downcast to, which is a terrible anti-pattern. Anything you'd do here with Any would be comparable in complexity to just changing AstNode to an enum.

您需要将 AST 的每个节点存储为一种类型,以便 (a) 提供您可能需要调用的所有方法 和 (b) 统一您可能需要调用的所有类型放入一个.选项 1 使用 trait 对象,而选项 2 使用枚举,但它们在原则上非常相似.第三种选择是使用 Any 来启用向下转换.

You need to store each node of the AST as a type that (a) provides all the methods you might need to call and (b) unifies all the types you might need to put in one. Option 1 uses trait objects, while option 2 uses enums, but they're pretty similar in principle. A third option is to use Any to enable downcasting.

这篇关于用于 AST 操作的 Rc 中的向下转换特征的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆