链接一系列拥有或引用的事物

时间:2018-09-21 21:36:57

标签: rust lifetime

我正在尝试为某些事物赋予特征,这些事物可以简单地包含其他事物,也可以根据事物的名称按需创建它们。反过来,那些包含的事物也应该能够做到这一点,从而创建各种层次结构。这是最少的代码:

use std::ops::Deref;

pub enum BoxOrRef<'a, T: ?Sized + 'a> {
    Boxed(Box<T>),
    Ref(&'a T),
}

impl<'a, T: ?Sized + 'a> Deref for BoxOrRef<'a, T> {
    type Target = T;
    fn deref(&self) -> &T {
        match self {
            BoxOrRef::Boxed(b) => &b,
            BoxOrRef::Ref(r) => r,
        }
    }
}

pub trait Elem {
    fn get_subelem<'a, 'b>(&'a self, name: &'b str) -> Option<BoxOrRef<'a, dyn Elem>>;
}

pub trait Table {
    fn get_elem<'a, 'b>(&'a self, name: &'b str) -> Option<BoxOrRef<'a, dyn Elem>>;
}

fn resolve_name<'a, T: Table + ?Sized>(
    table: &'a T,
    name: &[String],
) -> Option<BoxOrRef<'a, dyn Elem>> {
    let mut segments = name.iter();
    if let Some(first_segment) = segments.next() {
        segments.fold(table.get_elem(&first_segment), |res, next| {
            res.and_then(|elem| elem.get_subelem(next))
        })
    } else {
        None
    }
}

但是,生命周期检查器对此不满意:

error[E0597]: `elem` does not live long enough
  --> src/lib.rs:33:33
   |
33 |             res.and_then(|elem| elem.get_subelem(next))
   |                                 ^^^^                 - borrowed value only lives until here
   |                                 |
   |                                 borrowed value does not live long enough
   |
note: borrowed value must be valid for the lifetime 'a as defined on the function body at 26:17...
  --> src/lib.rs:26:17
   |
26 | fn resolve_name<'a, T: Table + ?Sized>(
   |                 ^^

我需要以某种方式延长中间res的寿命。我想我可以将它们放在一个结构中并调整resolve_name的返回类型,以将其与最终元素一起返回,但这使我感到相当笨拙。有更好的解决方案吗?

1 个答案:

答案 0 :(得分:3)

get_subelem的返回值不能超过您用来调用它的&self借项,因为get_subelem的签名如此明确地表示:

fn get_subelem<'a, 'b>(&'a self, name: &'b str) -> Option<BoxOrRef<'a, dyn Elem>>;
//                      ^^                                         ^^

为了获得BoxOrRef<'a, _>,您必须在生命周期self内借用'a。在调用方中,elem不能超过其所属的闭包,并且get_subelem借用了elem,因此它也无法返回可以逃避该闭包的值。

您正在尝试做一些不安全的事情,并且编译器可以阻止您。从理论上讲,table.get_elem可以返回一个Boxed值,而elem.get_subelem可以返回一个内部引用,然后当闭包返回时Box将被丢弃,从而使引用无效。

大概这实际上没有发生,所以您必须告诉编译器。一种方法是将&selfBoxOrRef<'a, _>分离:

pub trait Elem<'a> {
    fn get_subelem(&self, name: &str) -> Option<BoxOrRef<'a, dyn Elem<'a>>>;
}

以上更改将使您的示例在将生命周期参数添加到所有Elem时进行编译,但是在实现Elem时会使您陷入尴尬的境地:您无法返回对self,因此实际上所有内容都必须为Boxed

鉴于示例的含糊之处,很难做出很好的建议,但是我建议您退后一步,考虑一下BoxOrRef在这里是否是正确的抽象。从根本上讲,您无法对BoxOrRef进行任何操作,而您无法对引用进行操作,因为BoxOrRef可能成为。同时,您无法使用Box做任何事情,因为它可能是Boxstd::borrow::Cow使用ToOwned来实现Cloneinto_owned-也许类似的方法可能对您有用。 (如果可以的话,也许只需为ToOwned实现dyn Elem并直接使用Cow。)