我有一个IR的AST struct ,还有一个Data的 struct .这些 node 中的每一个都由几乎相同类型的 node 组成,但每个 node 都有一些只属于它们的 node .一般来说,IR可以包含像IfThenElse或函数应用这样的逻辑运算,而Data必须是更简单的记录.(但是,不要假设DataIR的严格子集--整个问题要复杂得多.)

我希望这些树的类型为IR,如果且仅当它们中的所有内容都类型为IR.我可以对具有固定数量的子 node 的所有 node 执行此操作,但不能对动态调整大小的 node (如List)执行此操作.

trait AST{} //Super trait that includes everything we're describing
trait IR{} //Tree that includes logic and operations
trait Data{} //Tree that only includes static data

为了避免大量冗余,我希望每种类型的 node 都指定一次,然后让它们可变地属于IR和/或Data.对于简单的 node ,足够简单:

//Literals can appear in any tree:
enum Literal{
    Int(i32),
    Bool(bool)
}
impl AST for Literal {}
impl Data for Literal {}
impl IR for Literal {}

//Variables can only appear in IR, not Data:
struct Variable(String);
impl AST for Variable {}
impl IR for Variable {}

//Assume we also have a type that is only valid as Data, not IR:
struct DataItem();
impl AST for DataItem {}
impl Data for DataItem {}

我们还有包含其他 node 的 node :

//IfThenElse is only IR, so the struct itself can have type requirements:
struct IfThenElse<Cond, Then, Else>(
    Cond,
    Then,
    Else
);
impl<Cond : AST + IR, Then :  AST + IR, Else :  AST + IR> AST for IfThenElse<Cond , Then, Else> {}
impl<Cond : AST + IR, Then :  AST + IR, Else :  AST + IR> IR for IfThenElse<Cond , Then, Else> {}

//Tuples can be valid IR, Data, or both, depending on their children:
struct Tuple<T1, T2>(T1, T2);
impl<T1 : AST, T2 : AST> AST for Tuple<T1, T2>{}
impl<T1 : IR + AST, T2 : IR + AST> IR for Tuple<T1, T2>{}
impl<T1 : Data + AST, T2 : Data + AST> Data for Tuple<T1, T2>{}

到目前为止,一切都完全按照我希望的那样工作,如下面的代码所示:

//Functions to assert that the type checking actually works:
fn accept_ir<T : IR>(t : &T){}
fn accept_data<T : Data>(t : &T){}
fn accept_ast<T : AST>(t : &T){}

fn tuple_type_check() {
    //Tuple with a Variable beneath it is IR:
    let ir_t  = Tuple(
        Variable("x".to_string()),
        Literal::Int(1)
    );
    accept_ir(&ir_t);

    //Tuple with only literals is both IR and Data:
    let both_t  = Tuple(
        Literal::Int(1),
        Literal::Bool(true)
    );
    accept_ir(&both_t);
    accept_data(&both_t);

    //Tuples can be nested, and the type checking will still work:
    let nested_ir_t  = Tuple(
        IfThenElse(
            Literal::Bool(true),
            Literal::Int(1),
            Literal::Int(2)
        ),
        Literal::Int(5)
    );
    accept_ir(&nested_ir_t);

    //And it knows all of these are AST:
    accept_ast(&ir_t);
    accept_ast(&both_t);
    accept_ast(&nested_ir_t);

    //Tuples which contain a mix of Data-only and IR-only items are neither:
    let malformed_t  = Tuple(
        DataItem(),
        Variable("x".to_string()),
    );
    
    // accept_ir(&malformed_t); //Won't compile (as it shouldn't)
    // accept_data(&malformed_t);
}

在上面的图中,您可以看到Tuple个 node 在其子 node 为Data时类型为Data,当子 node 为IR时为IR,并且这对嵌套树递归工作.

当我试图为其中包含动态集合的类型获取相同的行为时,我遇到了困难:

struct List<T>(
    Vec<T>
);
impl Data for List<Box<dyn Data>> {}
impl IR for List<Box<dyn IR>> {}
impl AST for List<Box<dyn AST>> {}

fn list_type_check() {
    //A list of only data is data...
    let data_l: List<Box<dyn Data>>  = List( //..but I have to explicitly declare it as a Data list
        vec![Box::new(Literal::Int(1)), Box::new(DataItem())]
    );
    accept_data(&data_l);

    //A list of literals should be Data AND IR
    let both_l: List<Box<dyn Data>>  = List( //..but I have pick just one to declare it as
        vec![Box::new(Literal::Int(1)), Box::new(Literal::Int(2))]
    );
    accept_data(&both_l);
    //accept_ir(&both_l); //and the other one won't compile..

    //Nesting works, but I have to declare the inner list as a list of dyn IR/Data too:
    let inner_l: List<Box<dyn IR>> = List(
        vec![
            Box::new(Variable("x".to_string())),
            Box::new(Variable("y".to_string())),
        ]
    );
    let outer_l : List<Box<dyn IR>> = List(
        vec![
            Box::new(inner_l),
            Box::new(Variable("l".to_string())),
        ]
    );
    accept_ir(&outer_l);
    
    //And none of these are also AST:
    // accept_ast(&data_l);
    // accept_ast(&both_l);
    // accept_ast(&outer_l);
}

这并不是完全失败,但:

  • 每个 node 都必须显式声明(好的推论已不复存在)
  • 如果我试图把AST作为一个超级特征,它就会崩溃(trait IR: AST {})
  • 价值观不能同时属于这两个特征,即使它们的内容符合要求

我还怀疑,有很多方法可以让我迈出一步,但在future ,这种 struct 的用处会大大降低.

推荐答案

我不认为您可以做很多事情来重新获得良好的推理,但您可以使用方法fn new_data(v: Vec<Box<dyn Data>>) -> List<Box<dyn Data>>来避免在代码中放置类型批注.

一个很好的改进是不是对Box<dyn Data>实现Data(对其他特性相同),而是一般地对T: Data实现它.这样,您还可以更高效地放置同构列表.要允许使用Box<dyn Data>,您需要添加impl<T: Data + ?Sized> Data for Box<T>.

这也解决了第二个问题,因为现在您一般实现AST,List<Box<dyn Data>>也隐含AST.

对于第三个问题,您可以创建一个特征DataAndIr: Data + IR和一个一揽子实现impl<T: Data + IR> DataAndIr for T.然后,您可以存储Box<dyn DataAndIr>.

把所有这些放在一起:

trait AST{}
trait IR : AST{}
trait Data : AST{}
struct List<T>(
    Vec<T>
);

impl<T : AST> AST for List<T> {}
impl<T: AST + ?Sized> AST for Box<T>{}
impl<T : Data> Data for List<T> {}
impl<T : Data + ?Sized> Data for Box<T>{}
impl<T : IR> IR for List<T> {}
impl<T: IR + ?Sized> IR for Box<T>{}
trait DataAndIr : Data + IR{}
impl<T : Data + IR> DataAndIr for T{}

//Helper functions to reduce boilerplate type declarations
fn data_list(v: Vec<Box<dyn Data>>) -> List<Box<dyn Data>> {
    List(v)
}
fn ir_list(v: Vec<Box<dyn IR>>) -> List<Box<dyn IR>> {
    List(v)
}


fn list_type_check() {
    let data_l = data_list( // function saves us from writing the type, at keast
        vec![Box::new(Literal::Int(1)), Box::new(DataItem())]
    );
    accept_data(&data_l);

    // A list of literals can be Data AND IR
    let both_l : List<Box<dyn DataAndIr>> = List(  //Need to declare it as both (or use a function) but it works
        vec![Box::new(Literal::Int(1)), Box::new(Literal::Int(2))]
    );
    accept_data(&both_l);
    accept_ir(&both_l);

    //Nesting still works:
    let outer_l = ir_list(
        vec![
            Box::new(ir_list(
                vec![
                    Box::new(Variable("x".to_string())),
                    Box::new(Variable("y".to_string())),
                ]
            )),
            Box::new(Variable("l".to_string())),
        ]
    );
    accept_ir(&outer_l);
    
    //And everything type checks as AST:
    accept_ast(&data_l);
    accept_ast(&both_l);
    accept_ast(&outer_l);
}

Rust相关问答推荐

在Rust中创建可变片段的可变片段的最有效方法是什么?

trait 中self 的显式生命周期似乎导致E0499无法在循环中多次borrow * emits 器作为可变的

如何从Rust记录WASM堆内存使用情况?

带扫描的铁 rust 使用滤镜

关于如何初始化弱 struct 字段的语法问题

有没有办法避免在While循环中多次borrow `*分支`

从Type::new()调用函数

如何修复&q;无法返回引用函数参数的值在异步规则中返回引用当前函数&q;拥有的数据的值?

使用 serde::from_value 反序列化为泛型类型

为什么 Rust 创建的 f32 小于 f32::MIN_POSITIVE?

如何将一个矩阵的列分配给另一个矩阵,纳尔代数?

Button.set_hexpand(false) 不会阻止按钮展开

为什么这段 Rust 代码会在没有递归或循环的情况下导致堆栈溢出?

一个函数调用会产生双重borrow 错误,而另一个则不会

使用 `clap` 在 Rust CLI 工具中设置布尔标志

Rust 中函数的类型同义词

如何在 Rust Polars 中可靠地连接 LazyFrames

仅当满足外部条件时如何添加到 actix web 的路由

使用部分键从 Hashmap 中检索值

在 Rust 中获得准确时间的正确方法?