是否有惯用的方法来保持对不断增长的容器元素的引用?

时间:2016-12-08 07:30:13

标签: rust lifetime borrow-checker

我试图为T类型的对象编写容器,该容器提供对存储对象的引用&T的访问(我想避免复制)。由于容器在其生命周期内只会增长,因此返回的引用&T的生命周期应与容器的生命周期相同。

我到目前为止最接近的是在容器内部使用Box<T>个对象,并使用Box<T>.as_ref()返回对这些对象的引用。然而,然而,我遇到了与这个最小例子中相同的问题:

fn main() {
    let mut v = vec![Box::new(1)]; // line 1
    let x = v[0].as_ref();         // line 2: immutable borrow occurs here
    println!("x = {:?}", x);       // line 3
    v.push(Box::new(2));           // line 4: mutable borrow occurs here -> error
    println!("x = {:?}", x);       // line 5
}

我理解,如果在可变借位期间从x删除v,则在第5行使用x将是不合理的。但情况并非如此,而且它永远不会出现在我的容器中。如果在Rust中没有安全的方式来表达这一点,我怎么能修复&#34;示例(不复制@model List<KeyValuePair<string, object>> <div> @foreach (KeyValuePair<string, object> item in Model) { Type type = item.Value.GetType(); switch (Type.GetTypeCode(type)) { case TypeCode.Boolean: { <input type="checkbox"/> break; } case TypeCode.String: { <input type="text"/> break; } default: { if (type == typeof (MyCustomType)) { ... } else if (type == typeof (MyOtherType)) { ... } } } } </div> )?

3 个答案:

答案 0 :(得分:5)

正如@ ker的回答所说,仅仅因为你只是一个容器,并不意味着引用保持有效,因为内存可以重新分配。

如果你只是在增长容器,那么另一种解决方案就是只存储索引而不是引用:

log(n)^2

答案 1 :(得分:2)

问题是您的Vec可能内存不足。如果发生这种情况,它必须分配一个新的(更大的)内存块,复制所有旧数据,并删除旧的内存分配。此时,对旧容器的所有引用都将变为无效。

您可以设计一个ropeVec Vec},其中,一旦内部vec已满,您只需创建一个新的,因此永远不会通过推送使指针无效

这需要一些不安全的代码,并且必须非常仔细地分析你必须跟上的借用规则。

答案 2 :(得分:1)

不幸的是,没有什么“可以随时使用”用于您的用例,但您可以编写一个包含Vec的包装器来执行不安全的操作,以便为您提供所需的功能,同时保证生锈需要

struct BoxVec<T>(Vec<Box<T>>);

impl<T> BoxVec<T> {
    pub fn new() -> Self { BoxVec(Vec::new()) }
    pub fn push<'a>(&'a mut self, t: T) -> &'a mut T {
        let mut b = Box::new(t);
        let t: &'a mut T = unsafe { std::mem::transmute::<&mut T, &'a mut T>(&mut b) };
        self.0.push(b);
        t
    }
    pub fn pusher<'a>(&'a mut self) -> BoxVecPusher<'a, T> {
        BoxVecPusher(self)
    }
}

struct BoxVecPusher<'a, T: 'a>(&'a mut BoxVec<T>);

impl<'a, T> BoxVecPusher<'a, T> {
    fn push<'b>(&'b mut self, t: T) -> &'a mut T {
        let mut b = Box::new(t);
        let t: &'a mut T = unsafe { std::mem::transmute::<&mut T, &'a mut T>(&mut b) };
        (self.0).0.push(b);
        t
    }
}

我选择的保证是你不能索引到Pusher,但是你得到了一个对新推送对象的可变引用。一旦释放推动器,您可以返回索引。

示例用法是

let mut v = BoxVec::new();
v.push(1);
let x = v[0];
let mut p = v.pusher();
let i = p.push(2);
p.push(3); // works now
let y = v[0]; // denied by borrow checker

Full example in the playground