Ответ 1
Нет, вы не можете перетащить Rc<Trait>
в Rc<Concrete>
, потому что объекты объектов типа Rc<Trait>
не содержат никакой информации о конкретном типе, которому принадлежат данные.
Здесь выдержка из официальной документации, которая применяется ко всем объектам признаков (&Trait
, Box<Trait>
, Rc<Trait>
):
pub struct TraitObject {
pub data: *mut (),
pub vtable: *mut (),
}
Поле data
указывает на собственно структуру, а поле vtable
указывает на набор указателей функций, по одному для каждого метода признака. Во время выполнения все, что у вас есть. И этого недостаточно для восстановления типа структуры. (С Rc<Trait>
блок data
указывает на то, что также содержит сильные и слабые подсчеты ссылок, но никакой дополнительной информации о типе.)
Но есть как минимум 3 других варианта.
Во-первых, вы можете добавить все операции, которые вам нужно сделать для Expression
или Condition
, к признаку AstNode
и реализовать их для каждой структуры. Таким образом, вам никогда не нужно вызывать метод, который недоступен для объекта-объекта, потому что этот признак содержит все необходимые методы.
Это также приводит к замене большинства элементов Rc<Expression>
и Rc<Condition>
в дереве Rc<AstNode>
, так как вы не можете отключить Rc<AstNode>
(но см. ниже о Any
):
enum Condition {
Equals(Rc<AstNode>, Rc<AstNode>),
LessThan(Rc<AstNode>, Rc<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) }
}
Вместо downcasting Rc<AstNode>
to Rc<Condition>
просто сохраните его как AstNode
и вызовите, например. rc.as_condition().unwrap().method_on_condition()
, если вы уверены, что rc
на самом деле является Rc<Condition>
.
Во-вторых, вы можете создать еще одно перечисление, объединяющее Condition
и Expression
, и полностью уничтожить объекты признаков. Это то, что я сделал в АСТ моего переводчика Схемы. С помощью этого решения не требуется нисходящее преобразование, потому что вся информация о типе присутствует во время компиляции. (Также с этим решением вам обязательно нужно заменить Rc<Condition>
или Rc<Expression>
, если вам нужно вынуть Rc<Node>
).
enum Node {
Condition(Condition),
Expression(Expression),
// you may add more here
}
impl Node {
fn children(&self) -> Vec<Rc<Node>> { ... }
}
Третий вариант заключается в использовании Any
и либо .downcast_ref()
, либо Rc::downcast
(в настоящее время только в ночное время) каждый Rc<Any>
в свой конкретный тип по мере необходимости.
Небольшое отклонение от этого было бы добавить метод fn as_any(&self) -> &Any { self }
в AstNode
, а затем вы можете вызвать методы Expression
(которые принимают &self
), написав node.as_any().downcast_ref::<Expression>().method_on_expression()
. Но в настоящее время нет (безопасно) upcast a Rc<Trait>
в Rc<Any>
, хотя нет реальной причины, по которой он не может работать.
Any
- это, строго говоря, самое близкое к ответу на ваш вопрос. Я не рекомендую это, потому что понижение, или необходимость понижения, часто является признаком плохого дизайна. Даже в языках с наследованием классов, например Java, если вы хотите сделать то же самое (например, хранить кучу узлов в ArrayList<Node>
), вам придется либо сделать все необходимые операции доступными на базе класс или где-то перечислять все подклассы, которые вам могут понадобиться для downcast, что является ужасным анти-шаблоном. Все, что вы сделали бы здесь с Any
, было бы сопоставимо по сложности, просто изменив AstNode
на перечисление.
tl; dr: Вам нужно сохранить каждый node AST как тип, который (a) предоставляет все методы, которые могут потребоваться для вызова и ( b) объединяет все типы, которые вам могут понадобиться, чтобы добавить один. Вариант 1 использует объекты признаков, в то время как вариант 2 использует перечисления, но они в принципе похожи. Третий вариант - использовать Any
для включения downcasting.
Связанные Q & A для дальнейшего чтения: