我担心这可能是非常基本的,但我还没有能够自己解决这个问题。我有这张地图:
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
?
答案 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>>