Rust生命周期问题:方法内无法重借用可变字段
实现
ScopedSet的with方法解决堆叠借用问题 问题背景
我想要实现一个名为ScopedSet的数据结构,它可变借用HashSet,向其中添加元素,并在ScopedSet被drop时自动移除该元素。基础用法如下:
let mut s = HashSet::new(); if let Some(mut s) = ScopedSet::new_with(&mut s, 1) { // 此时`s.set`包含`1`,执行相关操作 } // `s`不再包含`1`
已经完成的基础实现代码:
use std::collections::HashSet; use std::cmp::Eq; use std::hash::Hash; struct ScopedSet<'s, T: Eq + Hash> { value: T, set: &'s mut HashSet<T>, } impl<'s, T: Eq + Hash> Drop for ScopedSet<'s, T> { fn drop(&mut self) { self.set.remove(&self.value); } } impl<'s, T: Clone + Eq + Hash> ScopedSet<'s, T> { fn new_with(set: &'s mut HashSet<T>, value: T) -> Option<Self> { if set.insert(value.clone()) { Some(Self { value, set }) } else { None } } }
现在希望通过ScopedSet的方法实现堆叠添加元素,目标用法:
if let Some(mut s) = ScopedSet::new_with(&mut s, 1) { if let Some(mut s) = s.with(2) { // 此时`s.set`包含`1`和`2`,执行相关操作 } };
但尝试编写的with方法无法通过借用检查,代码及报错如下:
impl<'s, T: Clone + Eq + Hash> ScopedSet<'s, T> { fn with<'a, 'b>(&'a mut self, value: T) -> Option<ScopedSet<'b, T>> where 's: 'a, 'a: 'b, { if self.set.insert(value.clone()) { Some(Self { value, set: &mut self.set, }) } else { None } } }
编译报错:
error: lifetime may not live long enough --> src/main.rs:35:22 | 26 | impl<'s, T: Clone + Eq + Hash> ScopedSet<'s, T> { | -- lifetime `'s` defined here 27 | fn with<'a, 'b>(&'a mut self, value: T) -> Option<ScopedSet<'b, T>> | -- lifetime `'a` defined here ... 35 | set: &mut self.set, | ^^^^^^^^^^^^^ this usage requires that `'a` must outlive `'s` | = help: consider adding the following bound: `'a: 's`
解决方案
问题出在生命周期约束的逻辑错误:我们需要让新创建的ScopedSet的生命周期不超过当前ScopedSet的可变借用周期,而不是反向约束。正确的with方法实现如下:
impl<'s, T: Clone + Eq + Hash> ScopedSet<'s, T> { fn with<'a>(&'a mut self, value: T) -> Option<ScopedSet<'a, T>> { if self.set.insert(value.clone()) { Some(ScopedSet { value, set: &mut self.set, }) } else { None } } }
原理说明
- 这里的
'a是&'a mut self的生命周期,表示当前对ScopedSet的可变借用时长。 - 返回的
ScopedSet<'a, T>意味着新的ScopedSet所持有的HashSet引用,生命周期被限制在'a之内——也就是父ScopedSet的可变借用有效期内。 - 由于父
ScopedSet的set字段生命周期是's,而'a必然小于等于's(因为self本身的生命周期是's,可变借用&'a mut self要求'a <= 's),编译器可以安全地确保:- 新的
ScopedSet不会超出父ScopedSet的借用周期 - 父
ScopedSet不会超出原始HashSet的借用周期 - 当新的
ScopedSet被drop时,父ScopedSet的set仍然有效,可以正常执行移除操作
- 新的
验证用法
现在可以正常使用堆叠调用:
use std::collections::HashSet; fn main() { let mut s = HashSet::new(); if let Some(mut s1) = ScopedSet::new_with(&mut s, 1) { assert!(s1.set.contains(&1)); if let Some(mut s2) = s1.with(2) { assert!(s2.set.contains(&1)); assert!(s2.set.contains(&2)); // 执行相关操作 } // s2已drop,set中不再包含2 assert!(s1.set.contains(&1)); assert!(!s1.set.contains(&2)); } // s1已drop,set中不再包含1 assert!(!s.contains(&1)); }
内容的提问来源于stack exchange,提问作者Xavier
相关产品推荐
相关产品推荐

