简体   繁体   English

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

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

I want to create an ADT like:我想创建一个 ADT,如:

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

But apparently this is not the correct way to specify lifetimes.但显然这不是指定生命周期的正确方法。

My question is two-fold:我的问题有两个:

  1. What's the correct way to define lifetimes here?在这里定义生命周期的正确方法是什么?
  2. Is there a better way to tackle this problem?有没有更好的方法来解决这个问题? I don't want to clone everything each time, because everything is immutable, and because I don't want to create multiple copies.我不想每次都克隆所有内容,因为所有内容都是不可变的,而且我不想创建多个副本。 I also want to do stuff like我也想做类似的事情
let foo = Predicate::Bool(whatever args);
let and = Predicate::And(&foo, &foo);
let neg = Predicate::Neg(&and);
let bar = Predicate::And(&and, &neg);

and so forth and continue to be able to use foo , and , and neg later on.依此类推,以后可以继续使用fooandneg

If everything really is immutable, then the simplest way to handle this scenario is with Rc / Arc (the only difference between the two is that the latter can be used for objects accessed from multiple threads.) You can define your enum as follows:如果一切真的是不可变的,那么处理这种情况的最简单方法是使用Rc / Arc (两者之间的唯一区别是后者可用于从多个线程访问的对象。)您可以定义枚举如下:

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

An Rc is a reference-counted shared pointer to a heap-allocated object. Rc是一个引用计数的共享指针,指向堆分配的 object。 You can clone it to obtain a new pointer to the internal data without any copying, and you will never have to worry about lifetimes: internally, the Rc keeps track of how many references are being held to its object, and automatically deallocates it when this number drops to zero.您可以克隆它以获得指向内部数据的新指针而无需任何复制,并且您永远不必担心生命周期:在内部, Rc会跟踪对其 object 的引用数量,并在此情况下自动释放它数字下降到零。 This bookkeeping incurs a small runtime cost whenever the Rc is cloned or dropped, but greatly simplifies dealing with this sort of shared ownership.每当克隆或删除Rc时,这种簿记会产生很小的运行时成本,但极大地简化了处理这种共享所有权的过程。

Of course, the use of an Rc makes it somewhat more verbose to create predicates.当然,使用Rc使得创建谓词更加冗长。 The example you give would become:您给出的示例将变为:

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

(Technically not all these clones are necessary if you did not intend to use, say, neg later.) (从技术上讲,如果您以后不打算使用,例如, neg ,则并非所有这些克隆都是必需的。)

One way to ease this boilerplate is to use methods or associated functions on the Predicate type to create pre- Rc ed values, like so:简化此样板文件的一种方法是使用Predicate类型上的方法或相关函数来创建预Rc ed 值,如下所示:

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

With this, your example becomes:有了这个,你的例子变成:

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

There is one unavoidable downside to this approach, though.但是,这种方法有一个不可避免的缺点。 You cannot match through an Rc without dereferencing it first, which can make working with Rc ADTs somewhat painful.如果不先取消引用,就无法通过Rc进行match ,这会使使用Rc ADT 变得有些痛苦。 See this question for details.有关详细信息,请参阅此问题

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM