2016-10-13 20 views
16

Tôi đang cố gắng điều chỉnh AST trong Rust. Sẽ có rất nhiều thao tác, và tôi muốn cây của tôi không thay đổi được, do đó, để tiết kiệm thời gian, tất cả các tham chiếu sẽ là Rc s.Các đặc điểm Downcast bên trong Rc cho thao tác AST

nút cây của tôi sẽ giống như thế này:

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

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

Tôi muốn thay thế một nút ngẫu nhiên của một loại nhất định với một nút của cùng loại. Để thực hiện các hoạt động chung trên cây tôi đã tạo ra một đặc điểm:

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

Và tất cả các nút thực hiện điều này. Điều này cho phép tôi đi bộ cây mà không cần phải phá hủy từng loại nút cho mọi hoạt động, chỉ cần gọi children().

Tôi cũng muốn sao chép một nút trong khi chỉ cập nhật một trong các nút con của nó và để các nút khác ở đúng vị trí. Giả sử rằng tôi đã có thể tạo ra các nút của loại bê tông thích hợp (và tôi rất vui vì chương trình sẽ bị hoảng sợ nếu tôi sai). Tôi sẽ thêm các phương pháp sau đây để các đặc điểm:

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

Kế hoạch của tôi là để có những trẻ em được trả về bởi childen(), thay thế một trong số họ, và gọi clone_with_children() để xây dựng một nút của biến enum tương tự nhưng với một nút thay thế.

Vấn đề của tôi là cách viết clone_with_children().

tôi cần phải downCast Rc<AstNode>-Rc<Expression> (hoặc những gì có bạn), trong khi vẫn giữ refcount bên trong Rc giống nhau, nhưng không ai trong số các thư viện downcasting tôi đã tìm thấy dường như có khả năng làm điều đó.

Có phải những gì tôi muốn có thể hay tôi nên làm điều đó hoàn toàn khác?

+1

Bạn đã có một vấn đề khác sắp tới, trên thực tế, bạn không thể quay trở lại 'Self' (một đối tượng trait không thể sử dụng' Self' theo giá trị), bạn cần trả về 'Rc ' có thể. –

+0

Có lý do nào tại sao 'Expression' không thực hiện đặc điểm' AstNode', điều này cho phép công văn động làm đúng không? –

+1

Tôi nghĩ rằng thao tác AST là một sự phân tâm ở đây. Điều bạn thực sự muốn biết là liệu có thể downcast 'Rc 'thành' Rc ', trong đó' Object' thực hiện 'Trait', đúng không? – trentcl

Trả lời

7

Không, bạn không thể downcast Rc<Trait> đến Rc<Concrete>, vì các đối tượng trait như Rc<Trait> không chứa bất kỳ thông tin nào về loại dữ liệu cụ thể mà dữ liệu thuộc về.

Dưới đây là một đoạn trích từ the official documentation áp dụng cho tất cả các đối tượng đặc điểm (&Trait, Box<Trait>, Rc<Trait>):

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

Các data điểm trường vào struct chính nó, và các điểm vtable lĩnh vực để tập hợp các chức năng gợi ý , một cho mỗi phương pháp của đặc điểm. Vào thời gian chạy, đó là tất cả những gì bạn có. Và đó là không đủ để tái tạo lại loại cấu trúc. (Với Rc<Trait>, khối data điểm cũng chứa số lượng tham chiếu mạnh và yếu, nhưng không có thông tin loại bổ sung.)

Nhưng có ít nhất 3 tùy chọn khác.

Trước tiên, bạn có thể thêm tất cả các thao tác bạn cần thực hiện trên Expression s hoặc Condition s vào đặc điểm AstNode và triển khai chúng cho từng cấu trúc. Bằng cách này bạn không bao giờ cần phải gọi một phương thức không có sẵn trên đối tượng trait, bởi vì đặc điểm này chứa tất cả các phương thức bạn cần.

này cũng đòi hỏi phải thay thế hầu hết Rc<Expression>Rc<Condition> thành viên trong cây với Rc<AstNode>, vì bạn có thể không nhìn xuống Rc<AstNode> (nhưng xem dưới đây về Any):

enum Condition { 
    Equals(Rc<AstNode>, Rc<AstNode>), 
    LessThan(Rc<AstNode>, Rc<AstNode>), 
    ... 
} 
phương pháp

Một biến thể này có thể được viết trên AstNode mà phải mất &self và trở về tham chiếu đến các loại bê tông khác nhau:

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) } 
} 

Thay vì downcasting Rc<AstNode> đến Rc<Condition>, chỉ lưu trữ dưới dạng số AstNode và gọi điện, ví dụ: rc.as_condition().unwrap().method_on_condition(), nếu bạn tự tin rc thì thực tế là Rc<Condition>.

Thứ hai, bạn có thể tạo một enum khác hợp nhất ConditionExpression và loại bỏ hoàn toàn các đối tượng trait. Đây là những gì tôi đã làm trong AST của trình thông dịch Scheme của riêng tôi. Với giải pháp này, không yêu cầu downcasting vì tất cả các thông tin kiểu có mặt tại thời gian biên dịch. (Cũng với giải pháp này, bạn chắc chắn phải thay thế Rc<Condition> hoặc Rc<Expression> nếu bạn cần để có được một Rc<Node> ra khỏi nó.)

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

Một lựa chọn thứ ba là sử dụng Any, và một trong hai .downcast_ref() hoặc Rc::downcast (hiện chỉ trên mỗi đêm) Rc<Any> vào loại bê tông khi cần.

Một biến thể nhẹ vào đó sẽ có thêm một phương pháp fn as_any(&self) -> &Any { self } để AstNode, và sau đó bạn có thể gọi Expression phương pháp (mà phải mất &self) bằng cách viết node.as_any().downcast_ref::<Expression>().method_on_expression(). Nhưng hiện tại không có cách nào để (an toàn) upcast an Rc<Trait> đến một Rc<Any>, mặc dù không có lý do thực sự nào mà nó không thể hoạt động.

Any là, nói đúng nhất, điều gần nhất với câu trả lời cho câu hỏi của bạn. Tôi không khuyến nghị điều này vì downcasting, hoặc cần để downcast, thường là dấu hiệu của thiết kế kém. Ngay cả trong các ngôn ngữ với kế thừa lớp, như Java, nếu bạn muốn làm cùng một loại điều (lưu trữ một loạt các nút trong một ví dụ ArrayList<Node>), bạn phải thực hiện tất cả các hoạt động cần thiết có sẵn trên lớp cơ sở hoặc một nơi nào đó liệt kê tất cả các lớp con mà bạn có thể cần phải downcast, đó là một mô hình chống khủng khiếp. Bất cứ điều gì bạn làm ở đây với Any sẽ có thể so sánh về độ phức tạp để chỉ thay đổi AstNode thành một sự kiện.

tl; dr: Bạn cần lưu trữ mỗi nút của AST như một loại rằng (a) cung cấp tất cả những phương pháp bạn có thể cần phải gọi và (b) thống nhất tất cả các loại bạn có thể cần phải đặt vào một. Tùy chọn 1 sử dụng các đối tượng đặc điểm, trong khi tùy chọn 2 sử dụng enums, nhưng chúng khá giống nhau về nguyên tắc. Tùy chọn thứ ba là sử dụng Any để cho phép downcasting.

liên quan Q & Một để đọc thêm:

+0

Điều kỳ diệu duy nhất về 'Any' là nó sử dụng ['type_id'] (https://doc.rust-lang.org/stable/std/intrinsics/fn.type_id.html) nội tại thông qua [' TypeId: : of'] (https://doc.rust-lang.org/stable/std/any/struct.TypeId.html#method.of). Bất cứ ai cũng có thể viết lại 'Any' bằng cách sử dụng' TypeId :: of' như một khối xây dựng. :) –

+0

@ FrancisGagné mmm, đó không phải là sự hiểu biết của tôi. Chỉ các đối tượng đặc điểm 'Bất kỳ' chứa id loại. Bạn có thể viết * struct * của riêng bạn để chứa một kiểu id và viết các phương thức gửi đi trên nó, nhưng chỉ 'Any' mới có khả năng như một đối tượng trait. (Theo giải thích của tôi về tài liệu. Tôi mở để được hiển thị sai.) – trentcl

+1

'Bất kỳ :: get_type_id' cung cấp công văn động cho' TypeId :: of'. Một đối tượng đặc tính 'Any' không chứa trực tiếp kiểu id, nhưng bạn có thể truy cập vào kiểu id của kiểu cụ thể thông qua vtable. Bạn có thể sao chép và dán 'Any' và' impl 'từ thư viện chuẩn vào thùng của bạn và nó sẽ hoạt động tốt (mặc dù' Reflect' không ổn định). –

Các vấn đề liên quan