如何在Rust中实现可引用其他对象的Prop、Cat与Dict结构?
Rust中只读Dict的实现问题与解决方案
需求概述
- 定义一组属性(
Prop) - 定义一组类别(
Cat),每个类别由一个或多个Prop组成 - 实现一个字典(
Dict)存储上述内容,要求Prop和Cat在创建后变为只读
用户原代码
use std::collections::HashMap; struct Prop<'a> { name: &'a str } type PropVec<'a> = Vec<&'a Prop<'a>>; struct Cat<'a> { name: &'a str, props: PropVec<'a> } struct Dict<'a> { props: HashMap<&'a str, Prop<'a>>, cats: HashMap<&'a str, Cat<'a>>, } impl<'a> Dict<'a> { pub fn define_prop(&mut self, name: &'a str) { self.props.insert(name, Prop { name }); } pub fn prop(&self, name: &str) -> Option<&Prop> { self.props.get(name) } pub fn define_cat(&mut self, name: &'a str, prop_names: Vec<&'a str>) { let mut props: PropVec<'a> = Vec::new(); for name in prop_names { if let Some(prop) = self.prop(name) { props.push(prop); } } self.cats.insert(name, Cat { name: name, props }); } } fn main() { let mut d = Dict { props: HashMap::new(), cats: HashMap::new() }; d.define_prop("foo"); d.define_cat("bar", vec!("foo")); }
编译器报错信息
error: lifetime may not live long enough --> src/main.rs:29:24 | 19 | impl<'a> Dict<'a> { | -- lifetime `'a` defined here ... 28 | pub fn define_cat(&mut self, name: &'a str, prop_names: Vec<&'a str>) { | - let's call the lifetime of this reference `'1` 29 | let mut props: PropVec<'a> = Vec::new(); | ^^^^^^^^^^^ type annotation requires that `'1` must outlive `'a` error[E0502]: cannot borrow `self.cats` as mutable because it is also borrowed as immutable --> src/main.rs:37:9 | 19 | impl<'a> Dict<'a> { | -- lifetime `'a` defined here ... 29 | let mut props: PropVec<'a> = Vec::new(); | ----------- type annotation requires that `*self` is borrowed for `'a` ... 32 | if let Some(prop) = self.prop(name) { | --------------- immutable borrow occurs here ... 37 | self.cats.insert(name, Cat { name: name, props }); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ mutable borrow occurs here
用户疑问与解答
1. 有没有避免大量生命周期标注的实现方式?正确的生命周期标注方式是什么?
用普通引用的方案本质走不通,因为Rust的借用检查器不允许在持有Prop的不可变引用时,同时对Dict做可变借用(插入Cat)。更合理的方案是使用共享所有权,用Rc<T>存储Prop,这样Dict和Cat都持有Prop的共享所有权,无需生命周期标注。
修正后的实现:
use std::collections::HashMap; use std::rc::Rc; #[derive(Clone)] struct Prop { name: String, } type PropVec = Vec<Rc<Prop>>; struct Cat { name: String, props: PropVec, } struct Dict { props: HashMap<String, Rc<Prop>>, cats: HashMap<String, Cat>, } impl Dict { pub fn new() -> Self { Self { props: HashMap::new(), cats: HashMap::new(), } } pub fn define_prop(&mut self, name: &str) { let prop = Rc::new(Prop { name: name.to_string() }); self.props.insert(name.to_string(), prop); } pub fn prop(&self, name: &str) -> Option<Rc<Prop>> { self.props.get(name).cloned() } pub fn define_cat(&mut self, name: &str, prop_names: Vec<&str>) { let mut props = Vec::new(); for prop_name in prop_names { if let Some(prop) = self.prop(prop_name) { props.push(prop); } } self.cats.insert(name.to_string(), Cat { name: name.to_string(), props }); } } fn main() { let mut d = Dict::new(); d.define_prop("foo"); d.define_cat("bar", vec!("foo")); // 创建完成后转为不可变引用,实现只读 let d = d; // 此时无法再调用define_prop或define_cat }
如果坚持用引用方案,生命周期标注无法解决根本的借用冲突——在define_cat中,先不可变借用self获取Prop引用,之后又要可变借用self插入Cat,这违反了Rust“同一时间不能同时存在可变和不可变借用”的规则。
2. 第29行关于*self的注释需要关注吗?
需要关注。这条错误说明编译器认为self的临时借用生命周期('1)需要比Dict的生命周期('a)更长,但实际上&mut self的生命周期仅存在于函数调用期间,不可能覆盖Dict的整个生命周期,这是引用方案无法调和的矛盾,也是必须换用所有权方案的核心原因。
3. 能否获取Prop或其引用并存到其他地方?
用普通引用的话不行,因为Rust的借用规则禁止在持有Dict可变引用时同时持有内部Prop的不可变引用。但用Rc<Prop>的话,完全可以通过克隆Rc将Prop的共享所有权存储到Cat或其他地方,而且Prop的字段是不可变的(用String而非&str,且无修改方法),天然满足创建后只读的需求。
内容的提问来源于stack exchange,提问作者Araceli
相关产品推荐
相关产品推荐

