HashMap.insert()
返回给定键的旧值,而不是您刚刚传递的值。那不是你想要的!
将项目插入 后HashMap
,您必须调用HashMap.get()
以检索指向该值的指针。由于HashMap.insert()
拥有键和值的所有权,我们需要传递 to 的克隆,id
以便insert()
我们可以使用原始id
的进行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
inSubscription<'a>
很容易,因为我们收到了 in 具有正确生命周期的对象add
。满足'a
in&'a ...
更难,因为在Subscription<'a>
我们将其插入之前,该值没有固定地址subscriptions_map
(在我的示例中, aSubscription<'a>
从局部变量 in 移动main()
到参数 inFoo::add()
到 inside self.subscriptions_map
)。
为了满足外'a
,Foo::add()
必须将其self
参数定义为&'a mut self
。如果我们将其定义为&mut self
,我们无法确定我们得到的引用是否subscriptions_map
会存在足够长的时间(它们的生命周期可能比 短'a
)。
然而,通过在 a 中插入一个&'a Subscription<'a>
内部Foo<'a>
,我们有效地锁定了进一步的Foo
修改,因为我们现在存储了一个来自self.subscriptions_map
in的借用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()
是,由于'a
inFoo<'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));
}