0

恐怕这可能是非常基本的,但我自己无法弄清楚。我有这张地图:

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

4

1 回答 1

1

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

将项目插入 后HashMap,您必须调用HashMap.get()以检索指向该值的指针。由于HashMap.insert()拥有键和值的所有权,我们需要传递 to 的克隆,id以便insert()我们可以使用原始id的进行get()调用。(如果类型idCopy,您可以省略调用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>>. 满足'ainSubscription<'a>很容易,因为我们收到了 in 具有正确生命周期的对象add。满足'ain&'a ...更难,因为在Subscription<'a>我们将其插入之前,该值没有固定地址subscriptions_map(在我的示例中, aSubscription<'a>从局部变量 in 移动main()到参数 inFoo::add()到 inside self.subscriptions_map)。

为了满足外'aFoo::add()必须将其self参数定义为&'a mut self。如果我们将其定义为&mut self,我们无法确定我们得到的引用是否subscriptions_map会存在足够长的时间(它们的生命周期可能比 短'a)。

然而,通过在 a 中插入一个&'a Subscription<'a>内部Foo<'a>,我们有效地锁定了进一步的Foo修改,因为我们现在存储了一个来自self.subscriptions_mapin的借用self.subscriptions。考虑如果我们在 中插入另一个项目会发生什么subscriptions_map:我们如何确保HashMap不会在内存中移动其项目?如果HashMap确实移动了我们的项目,则指针self.subscriptions不会自动更新并且会悬空。

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

impl<'a> Foo<'a> {
    fn remove(&mut self, id: &SubscriptionKey) {
        self.subscriptions_map.remove(id);
    }
}

这个方法编译得很好。但是,如果我们尝试在之前调用的a 上调Foo​​用它add(),那么self.subscriptions将包含对曾经在 中的项目的悬空引用self.subscriptions_map

所以可变借用在调用后仍然存在的原因add()是,由于'ainFoo<'a>等于Foo<'a>自身的生命周期,编译器看到对象从自身借用。如你所知,我们不能同时拥有一个可变借用和另一个有效借用(可变或非可变借用),因此 Rust 会阻止我们在保留一个活跃借用的f同时进行可变借用。f事实上,由于我们使用了一个self通过可变引用获取的方法,Rust 假设Foo<'a>存储了一个可变引用,即使情况并非如此,因为 Rust 只查看签名来确定借用(这是为了确保将私有字段从&'a T&'a mut T不会导致借阅检查失败给您,如果您正在开发图书馆,给您的用户)。由于对象的类型永远不会改变,因此在Foo<'a>其剩余的生命周期内都将被锁定。

现在,你能做什么?显然,你不能Vec<&'a Subscription<'a>>在你的结构中有一个有用的。HashMap提供了一个values()迭代器,但它以未指定的顺序枚举值,因此如果您想以添加它们的顺序枚举值,它将无济于事。您可以使用以下命令,而不是使用借来的指针Rc

use std::collections::HashMap;
use std::rc::Rc;

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

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

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

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

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

    f.add(SubscriptionKey, Subscription(subscription_data));
}
于 2016-03-22T01:42:23.730 回答