创建递归枚举——我应该使用生命周期引用吗? (锈)

Creating recursive enum -- should I use lifetime references? (Rust)

我想创建一个像这样的 ADT:

pub enum Predicate<'a>{
    And(Box<&'a Predicate>, Box<&'a Predicate>),
    Neg(Box<&'a Predicate>),
    Bool(LogicOp, Literal, Literal)
}

但显然这不是指定生命周期的正确方法。

我的问题有两个:

  1. 此处定义生命周期的正确方法是什么?
  2. 有没有更好的方法来解决这个问题? 我不想每次都克隆所有内容,因为所有内容都是不可变的,而且我不想创建多个副本。我也想做
let foo = Predicate::Bool(whatever args);
let and = Predicate::And(&foo, &foo);
let neg = Predicate::Neg(&and);
let bar = Predicate::And(&and, &neg);

等等,以后可以继续使用fooandneg

如果一切真的都是不可变的,那么处理这种情况的最简单方法是使用 Rc/Arc(两者之间的唯一区别是后者可用于从多个线程。)您可以按如下方式定义您的枚举:

pub enum Predicate {
    And(Rc<Predicate>, Rc<Predicate>),
    Neg(Rc<Predicate>),
    Bool(LogicOp, Literal, Literal)
}

Rc 是指向堆分配对象的引用计数共享指针。您可以克隆它以获得指向内部数据的新指针而无需任何复制,并且您永远不必担心生命周期:在内部,Rc 会跟踪对其对象持有多少引用,并自动当这个数字下降到零时释放它。每当克隆或删除 Rc 时,这种簿记都会产生少量运行时成本,但会大大简化处理此类共享所有权的过程。

当然,使用 Rc 会使创建谓词变得更加冗长。您给出的示例将变为:

let foo = Rc::new(Predicate::Bool(whatever args));
let and = Rc::new(Predicate::And(foo.clone(), foo.clone()));
let neg = Rc::new(Predicate::Neg(and.clone()));
let bar = Rc::new(Predicate::And(and.clone(), neg.clone()));

(如果您不打算稍后使用 neg,从技术上讲,并非所有这些克隆都是必需的。)

简化此样板的一种方法是在 Predicate 类型上使用方法或关联函数来创建预先 Rced 的值,如下所示:

impl Predicate {
    pub fn and(a: &Rc<Predicate>, b: &Rc<Predicate>) -> Rc<Predicate> {
        Rc::new(Predicate::And(a.clone(), b.clone())
    }

    pub fn neg(a: &Rc<Predicate>) -> Rc<Predicate> {
        Rc::new(Predicate::Neg(a.clone()))
    }

    pub fn boolean(op: LogicOp, a: Literal, b: Literal) -> Rc<Predicate> {
        Rc::new(Predicate::Bool(op, a, b))
    }
}

有了这个,你的例子就变成了:

let foo = Predicate::boolean(whatever args);
let and = Predicate::and(&foo, &foo);
let neg = Predicate::neg(&and);
let bar = Predicate::and(&and, &neg);

不过,这种方法有一个不可避免的缺点。你不能 match 通过 Rc 而不先取消引用它,这会使使用 Rc ADTs 有点痛苦。有关详细信息,请参阅