来自Option <t <'a>的Aquire&amp;&gt;&gt;

时间:2016-03-21 18:03:47

标签: reference rust borrow-checker

我担心这可能是非常基本的,但我还没有能够自己解决这个问题。我有这张地图:

subscriptions_map: HashMap<SubscriptionKey, Subscription<'a>>

和这个载体:

subscriptions: Vec<&'a Subscription<'a>>,

我想在HashMap中插入一个值,并在向量中引用同一个项目。我试过这样做:

let subs: &'a Subscription = &self.subscriptions_map.insert(id, item).unwrap();
self.subscriptions.push(subs);

但它得到了这个错误:

error: borrowed value does not live long enough
         let subs: &'a Subscription = &self.subscriptions_map.insert(id, item).unwrap();
                                       ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
note: reference must be valid for the lifetime 'a as defined on the block at 40:70...
     pub fn add_subscription(&'a mut self, mut item: Subscription<'a>) {
         let id = item.get_id();

         let _lock = self.lock.lock().unwrap();

         let subs: &'a Subscription = &self.subscriptions_map.insert(id, item).unwrap();
 ...
note: ...but borrowed value is only valid for the block suffix following statement 2 at 45:87
         let subs: &'a Subscription = &self.subscriptions_map.insert(id, item).unwrap();
         self.subscriptions.push(subs);
     }
error: aborting due to previous error

我想我的问题归结为:如果我有Option<T<'a>>,我该如何获得&'a T

1 个答案:

答案 0 :(得分:1)

HashMap.insert()会返回给定键的值,而不是您刚刚传递的值。那不是你想要的!

在您将项目插入HashMap后,您必须调用HashMap.get()来检索指向该值的指针。由于HashMap.insert()取得了密钥和值的所有权,我们需要将id的克隆传递给insert(),以便我们可以将原始id用于{{1}调用。 (如果get()的类型为id,则可以省略对Copy的调用,并让编译器复制该值。)

clone()

这样可以正常工作,但如果我们尝试添加其他订阅则会崩溃。如果我们这样做:

use std::collections::HashMap;

#[derive(Eq, PartialEq, Hash, Clone)]
struct SubscriptionKey;
struct Subscription<'a>(&'a ());

struct Foo<'a> {
    subscriptions_map: HashMap<SubscriptionKey, Subscription<'a>>,
    subscriptions: Vec<&'a Subscription<'a>>,
}

impl<'a> Foo<'a> {
    fn add(&'a mut self, id: SubscriptionKey, item: Subscription<'a>) {
        self.subscriptions_map.insert(id.clone(), item);
        let subs = self.subscriptions_map.get(&id).unwrap();
        self.subscriptions.push(subs);
    }
}

fn main() {
    let subscription_data = &();

    let mut f = Foo {
        subscriptions_map: HashMap::new(),
        subscriptions: Vec::new(),
    };

    f.add(SubscriptionKey, Subscription(subscription_data));
}

编译器提供以下消息:

fn main() {
    let subscription_data = &();
    let subscription_data2 = &();

    let mut f = Foo {
        subscriptions_map: HashMap::new(),
        subscriptions: Vec::new(),
    };

    f.add(SubscriptionKey, Subscription(subscription_data));
    f.add(SubscriptionKey, Subscription(subscription_data2));
}

发生了什么?为什么在第一次调用<anon>:30:5: 30:6 error: cannot borrow `f` as mutable more than once at a time [E0499] <anon>:30 f.add(SubscriptionKey, Subscription(subscription_data2)); ^ <anon>:30:5: 30:6 help: see the detailed explanation for E0499 <anon>:29:5: 29:6 note: previous borrow of `f` occurs here; the mutable borrow prevents subsequent moves, borrows, or modification of `f` until the borrow ends <anon>:29 f.add(SubscriptionKey, Subscription(subscription_data)); ^ <anon>:31:2: 31:2 note: previous borrow ends here <anon>:20 fn main() { ... <anon>:31 } ^ 之后,可变借用仍然存在?

问题来自Foo::add字段的定义。它被定义为subscriptions。满足Vec<&'a Subscription<'a>>中的'a非常简单,因为我们会在Subscription<'a>中收到具有正确生命周期的对象。满足add中的'a比较困难,因为&'a ...值没有固定地址,直到我们将其插入Subscription<'a>(在我的示例中,{ {1}}已从subscriptions_map中的本地变量移至Subscription<'a>中的参数移至main()内部。

为了满足外部Foo::add()self.subscriptions_map必须将其'a参数定义为Foo::add()。如果我们将其定义为self,我们无法确定我们从&'a mut self获得的引用是否足够长(它们的生命周期可能短于&mut self)。< / p>

但是,通过在subscriptions_map内插入'a,我们实际上锁定 &'a Subscription<'a>以进行进一步修改,因为我们现在正在存储借用Foo<'a>中的Foo借用。考虑如果我们在self.subscriptions_map中插入另一个项目会发生什么:我们怎样才能确保self.subscriptions在内存中不会移动其项目?如果subscriptions_map确实移动了我们的项目,则HashMap中的指针不会自动更新并且会悬空。

现在,假设我们有这个错误的HashMap方法:

self.subscriptions

此方法编译良好。但是,如果我们之前尝试在remove()上调用此impl<'a> Foo<'a> { fn remove(&mut self, id: &SubscriptionKey) { self.subscriptions_map.remove(id); } } ,那么Foo将包含对曾在add()中的项目的悬空引用

因此,在调用self.subscriptions之后可变借用持续存在的原因是,因为self.subscriptions_map中的add()等于'a本身的生命周期,编译器看到对象借用了自己。如你所知,我们不能同时进行可变借入和另一次借用(可变或不可用),因此Rust阻止我们在Foo<'a>时对Foo<'a>进行可变借用保留积极的借款。实际上,由于我们使用了一个通过可变引用获取f的方法,因此Rust假定f存储了一个可变引用,即使事实并非如此,因为Rust只查看签名确定借款(这是为了确保将私人字段从self更改为Foo<'a>不会导致您的借用检查失败,如果您正在开发库,则会导致用户)。由于对象的类型永远不会改变,&'a T在其整个生命周期内都会被锁定。

现在,你能做什么?显然,您在结构中有一个&'a mut T非常有用。 Foo<'a>提供了values()迭代器,但它以未指定的顺序枚举值,因此如果您想按照添加顺序枚举值,它将不会帮助您。您可以使用Rc

,而不是使用借来的指针
Vec<&'a Subscription<'a>>