问题描述
我想创建一个 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
的值,如下所示:
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
ADT 有点痛苦。有关详细信息,请参阅 this question。