2016-10-13 8 views
16

私はRustのASTを操作しようとしています。たくさんの操作があり、私は木を不変にしたいので、時間を節約するためにすべての参照はRcです。AST操作のためのRc内のダウンキャスト形質

マイツリーノードは次のようになります。

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

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

は、私は、同じタイプの別のノードに指定されたタイプのランダムなノードを交換したいです。ツリー上の汎用操作を行うために、私は特性を作りました:

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

そして、すべてのノードがこれを実装しています。これにより、各オペレーションの各ノードタイプを破棄せずに、単にchildren()と呼ぶだけでツリーを歩くことができます。

また、ノードのクローンを作成してそのうちの1つのみを更新し、残りのノードはそのままにしたいと考えています。適切な具体的なタイプのノードを生成できたとします(私が間違っているとプログラムがうんざりするのはうれしいです)。私の計画は、childen()によって返された子供たちを取るそれらのいずれかを交換し、同じ列挙型変異体のノードを構築するためにclone_with_children()を呼び出すことです

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

が、1つのノードを持つ:私は、形質に次のメソッドを追加します置き換えられました。

私の問題は、clone_with_children()を書く方法です。 Rc同じ内部の参照カウントを維持しながら

私は、Rc<Expression>からRc<AstNode>(または何を持っている)をダウンキャストする必要がありますが、私が見つけたダウンキャストライブラリのどれもがそれを行うことができるように思えません。

私は可能なのですか、それとも全く違うのですか?

+1

さらに読書のための関連Q & A 'Self'(特性オブジェクトは値で' Self'を使うことはできません)、おそらく 'Rc 'を返す必要があります。 –

+0

'Expression'が' AstNode'特性を実装していない理由はありますか?それは動的ディスパッチが正しいことをさせるでしょうか? –

+1

私はASTの操作はここで気を散らすと思います。本当に知りたいのは、 'Rc 'を 'Rc 'にダウンキャストすることができるかどうかです。 'Object'は' Trait'を実装していますか? – trentcl

答えて

7

いいえ、Rc<Trait>Rc<Concrete>のような特性オブジェクトは、Rc<Trait>のような特性オブジェクトには、データが属する具体的なタイプの情報が含まれていないため、ダウンキャストできません。

ここでは、すべての形質オブジェクト(&TraitBox<Trait>Rc<Trait>)に適用されるthe official documentationからの抜粋です:関数ポインタのコレクションに

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

構造体自体へdata視野点、及びvtableフィールドポイントは、その形質の各方法について1つ。実行時にはそれだけです。そして、それは構造体の型を再構築するのに十分ではありません。 (Rc<Trait>の場合、ブロックdataには強い参照カウントと弱い参照カウントも含まれていますが、追加の型情報は含まれません)。

しかし、少なくとも3つのオプションがあります。

最初にExpressionまたはConditionに行う必要がある操作をすべてAstNodeの特性に追加し、各構造体に実装することができます。このように、形質には必要なすべてのメソッドが含まれているため、形質オブジェクトでは利用できないメソッドを呼び出す必要はありません。

あなたはRc<AstNode>意気消沈(が、約Any下記参照)することはできませんので、これはまた、Rc<AstNode>とツリーに最もRc<Expression>Rc<Condition>メンバーを交換することを必要と:この上の変化は上のメソッドを記述される可能性があります

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

&selfを取り、様々な具体的なタイプへの参照を返すAstNode

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<Condition>の場合はAstNodeとして保存してください。 rc.as_condition().unwrap().method_on_condition()、確信している場合rcは実際にRc<Condition>です。

第2に、ConditionExpressionを統一する別の列挙型を作成して、特性オブジェクトを完全に削除することができます。これは自分のスキームインタープリタのASTでやったことです。このソリューションでは、すべての型情報がコンパイル時に存在するため、ダウンキャストは不要です。 (あなたはそれのうちRc<Node>を取得する必要がある場合にも、このソリューションで、あなたは間違いなくRc<Condition>またはRc<Expression>を交換する必要があります。)

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>を必要に応じて具体的に入力します。その上

わずかな変動がAstNodeにメソッドfn as_any(&self) -> &Any { self }を追加することです、そしてあなたはnode.as_any().downcast_ref::<Expression>().method_on_expression()を書き込むことによって(&selfを取る)Expressionメソッドを呼び出すことができます。しかし、現実にはうまくいっていないにもかかわらず、(安全に)upcastRc<Trait>からRc<Any>に転送する方法は現在ありません。

Anyは厳密に言えば、あなたの質問に対する答えに最も近いものです。ダウンキャスティング、またはダウンキャストにが必要なを必要とするため、私はそれをお勧めしませんが、しばしばデザインが不良であることを示しています。 Javaのようにクラス継承を持つ言語であっても、同じ種類のもの(例えば、ArrayList<Node>に多数のノードを格納する)を実行する場合は、必要なすべての操作を基本クラスで使用可能にするか、あなたがダウンキャストする必要があるかもしれないすべてのサブクラスを列挙します。これは恐ろしい反パターンです。 Anyでここで行うことは、AstNodeを列挙型に変更するだけの複雑さに匹敵します。

TL; DR:あなたは(a)のあなたはを呼び出す必要がありますすべてのメソッドを提供し、(b)はは、あなたがする必要があるかもしれませんすべての種類を統一することを型としてASTの各ノードを格納する必要があります1つ入れて。オプション1は特性オブジェクトを使用し、オプション2はenumを使用しますが、原理的には似ています。第3の選択肢は、ダウンキャスティングを可能にするためにAnyを使用することである。あなたが返すことができません:あなたは実際には別の問題到来を、持っている

+0

'Any'の唯一の魔法は、[' TypeId:](https://doc.rust-lang.org/stable/std/intrinsics/fn.type_id.html)の組み込み関数を使って['TypeId: :of'](https://doc.rust-lang.org/stable/std/any/struct.TypeId.html#method.of)。誰でも 'TypeId :: of'をビルディングブロックとして' Any'を書き換えることができました。 :) –

+0

@FrancisGagnéねえ、それは私の理解ではありません。 'Any' traitオブジェクトだけが型idを含みます。型IDを格納し、そこにディスパッチするメソッドを書くために独自の* struct *を書くことができますが、 'Any'だけがその特性を特性オブジェクトとして取得します。 (私の解釈では、私は間違って表示されています。) – trentcl

+1

'Any :: get_type_id'は' TypeId :: of'の動的ディスパッチを提供します。 'Any'特性オブジェクトには直接型IDが含まれていませんが、vtableを介して具体型の型IDにアクセスできます。あなたは 'Any'と' impl'sを標準ライブラリからコピーしてあなたのクレートに貼り付けることができ、それはうまくいくでしょう( 'Reflect'は不安定です)。 –

関連する問題