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

问题描述

我想创建一个 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 类型上使用方法或关联函数来创建预先Rc 的值,如下所示:

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

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

相关问答

错误1:Request method ‘DELETE‘ not supported 错误还原:...
错误1:启动docker镜像时报错:Error response from daemon:...
错误1:private field ‘xxx‘ is never assigned 按Alt...
报错如下,通过源不能下载,最后警告pip需升级版本 Requirem...