创建递归枚举——我应该使用生命周期引用吗? (锈)
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)
}
但显然这不是指定生命周期的正确方法。
我的问题有两个:
- 此处定义生命周期的正确方法是什么?
- 有没有更好的方法来解决这个问题?
我不想每次都克隆所有内容,因为所有内容都是不可变的,而且我不想创建多个副本。我也想做
let foo = Predicate::Bool(whatever args);
let and = Predicate::And(&foo, &foo);
let neg = Predicate::Neg(&and);
let bar = Predicate::And(&and, &neg);
等等,以后可以继续使用foo
、and
和neg
。
如果一切真的都是不可变的,那么处理这种情况的最简单方法是使用 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
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))
}
}
有了这个,你的例子就变成了:
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 有点痛苦。有关详细信息,请参阅 。
我想创建一个像这样的 ADT:
pub enum Predicate<'a>{
And(Box<&'a Predicate>, Box<&'a Predicate>),
Neg(Box<&'a Predicate>),
Bool(LogicOp, Literal, Literal)
}
但显然这不是指定生命周期的正确方法。
我的问题有两个:
- 此处定义生命周期的正确方法是什么?
- 有没有更好的方法来解决这个问题? 我不想每次都克隆所有内容,因为所有内容都是不可变的,而且我不想创建多个副本。我也想做
let foo = Predicate::Bool(whatever args);
let and = Predicate::And(&foo, &foo);
let neg = Predicate::Neg(&and);
let bar = Predicate::And(&and, &neg);
等等,以后可以继续使用foo
、and
和neg
。
如果一切真的都是不可变的,那么处理这种情况的最简单方法是使用 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
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))
}
}
有了这个,你的例子就变成了:
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 有点痛苦。有关详细信息,请参阅