注意:在本回答中,我将使用
dyn Trait
语法,以便更清楚地表示类型是一个trait对象。旧的编写
Rc<dyn Trait>
的方式是
Rc<Trait>
。参见
“类型中的“dyn”是什么意思?。
不,你不能将
Rc<dyn Trait>
向下转换为
Rc<Concrete>
,因为像
dyn Trait
这样的trait对象不包含有关数据所属的具体类型的任何信息。
以下是适用于所有指向trait对象(
&dyn Trait
、
Box<dyn Trait>
、
Rc<dyn Trait>
等)的指针的
官方文档摘录:
pub struct TraitObject {
pub data: *mut (),
pub vtable: *mut (),
}
data
字段指向结构体本身,vtable
字段指向一个函数指针集合,每个trait方法对应一个函数指针。在运行时,这就是你拥有的全部内容。但这不足以重构结构体的类型。(使用Rc<dyn Trait>
时,data
块还包含强引用和弱引用计数,但没有其他类型信息。)
但至少有3种其他选择。
将公共行为放入trait中
首先,您可以将需要在Expression
或Condition
上执行的所有操作添加到trait AstNode
中,并为每个结构体实现它们。这样,您永远不需要调用在trait对象上不可用的方法,因为trait包含您需要的所有方法。
这可能需要将树中的大多数Rc<Expression>
和Rc<Condition>
成员替换为Rc<dyn AstNode>
,因为您无法向下转换Rc<dyn AstNode>
(但请参见下面有关Any
的内容):
enum Condition {
Equals(Rc<dyn AstNode>, Rc<dyn AstNode>),
LessThan(Rc<dyn AstNode>, Rc<dyn AstNode>),
...
}
这个的变体可能是在 AstNode
上编写方法,这些方法采用 &self
并返回到各种具体类型的引用:
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<dyn AstNode>
强制转换为 Rc<Condition>
,只需将其存储为 AstNode
并调用例如 rc.as_condition().unwrap().method_on_condition()
,如果您确信 rc
实际上是一个 Rc<Condition>
。
加倍使用 enum
其次,您可以创建另一个枚举类型,将 Condition
和 Expression
统一起来,完全放弃 trait 对象。这就是我在自己的 Scheme 解释器的 AST 中所做的。使用此解决方案,不需要强制转换,因为所有类型信息都在枚举变量中。(此外,使用此解决方案,如果需要从中获取 Rc<Node>
,则绝对需要替换 Rc<Condition>
或 Rc<Expression>
。)
enum Node {
Condition(Condition),
Expression(Expression),
}
impl Node {
fn children(&self) -> Vec<Rc<Node>> { ... }
}
使用Any
进行下溯转换
第三种选择是使用Any
,并根据需要将每个Rc<dyn Any>
向其具体类型的Rc::downcast
。
稍微变化一下,可以在AstNode
中添加一个方法fn as_any(&self) -> &Any { self }
,然后通过编写node.as_any().downcast_ref::<Expression>().method_on_expression()
来调用Expression
方法(接受&self
)。但目前还没有办法(安全地)向上转换Rc<dyn Trait>
到Rc<dyn Any>
(尽管这可能会在未来发生改变)。
Any
严格来说是最接近你问题答案的东西。但我不建议使用它,因为向下转型或需要向下转型通常表明设计有缺陷。即使在具有类继承的语言(如Java)中,如果您想要做同样的事情(例如在ArrayList<Node>
中存储一堆节点),您必须将所有所需操作都放在基类上或者枚举可能需要向下转换的所有子类,这是一个可怕的反模式。在这里使用Any
所做的任何事情都与将AstNode
更改为枚举相当复杂。
TL;DR
您需要将AST的每个节点存储为一种类型,该类型提供了您可能需要调用的所有方法,并统一了您可能需要放入其中的所有类型。选项1使用特质对象,而选项2使用枚举,但原则上它们非常相似。第三种选择是使用Any
启用向下转型。
另请参阅
Self
(特质对象无法通过值使用Self
),你可能需要返回Rc<AstNode>
。 - Matthieu M.Rc<Trait>
向下转换为Rc<Object>
,其中Object
实现了Trait
,对吗? - trentExpression
确实实现了AstNode
,我希望这一点是显而易见的。但这对于向下转型并没有什么帮助。 - rix0rrrAstNode::clone_with_children
返回了一个Rc<AstNode>
,它不能替换条件和表达式中的Rc<Expression>
,鉴于缺少代码示例... 我可以看到许多绕过这个问题的方法,但很难预测哪些有用,哪些是无用的... - Matthieu M.