Rust 如何同时获取多个结构体字段的可变引用?
结论
Rust 允许同时持有指向不同结构体字段的可变引用,也允许同时借用 Vec 中不同元素,前提是这些借用互不重叠。问题中的访问器方法每次都会可变借用整个 MyStruct。因此,调用 x.a() 并保留返回值后,再调用 x.b() 就会发生借用冲突。可以在一次借用中拆分字段,也可以用能证明元素互不重叠的切片操作。
原代码还有两处类型和语法问题:字段 b 的类型是 f32,示例变量却声明为 &mut i32;入口函数应写作 fn main() { ... }。
同时借用不同字段
直接借用不同字段时,Rust 能识别它们彼此独立:
pub struct MyStruct {
a: i64,
b: f32,
c: Vec<bool>,
}
fn main() {
let mut x = MyStruct {
a: 34,
b: 23.023,
c: vec![true, false, true, false],
};
let a: &mut i64 = &mut x.a;
let b: &mut f32 = &mut x.b;
*a += 1;
*b += 1.0;
}
如果要通过方法访问这些字段,可以让一个方法同时借用它们,并在 MyStruct 内部拆分借用:
impl MyStruct {
pub fn a_b(&mut self) -> (&mut i64, &mut f32) {
(&mut self.a, &mut self.b)
}
}
fn main() {
let mut x = MyStruct {
a: 34,
b: 23.023,
c: vec![true, false, true, false],
};
let (a, b) = x.a_b();
*a += 1;
*b += 1.0;
}
原有的 a()、b() 和 c() 都以 &mut self 为接收者。每个返回的引用都会独占借用接收者;第一个引用还在使用时,Rust 就不允许再通过另一个访问器借用 x。让同一个方法返回多个字段的引用,Rust 就能在方法内部确认它们指向不同字段。
同时借用 Vec 中的不同元素
连续调用两次 x.c(index),无法安全地证明两个索引不同。要借用两个不同位置,可以用 split_at_mut 将切片拆成互不重叠的部分:
let (left, right) = x.c.split_at_mut(3);
let c0: &mut bool = &mut left[0];
let c3: &mut bool = &mut right[0];
*c0 = false;
*c3 = true;
split_at_mut(3) 将索引 0..3 和 3.. 分开,所以 left[0] 和 right[0] 分别对应原向量中的索引 0 和 3。调用前,向量长度至少要为 4,否则索引会越界。如果索引来自运行时输入,还要检查它们是否在范围内、是否不同,再按较小的索引拆分切片。
嵌套结构体中的引用与释放
这取决于方法返回的引用绑定到哪个生命周期。如果方法通过 &mut self 返回引用,返回值通常受这次对结构体 4 的可变借用约束。只要还在使用返回的引用,就不能释放或再次可变借用结构体 4。
如果结构体 4 保存着指向外部数据的可变引用,并通过类型参数保留了该数据的生命周期,可以设计一个消费 self 并将引用返回给调用者的方法。简化形式如下:
struct S1 {
value: i32,
}
struct S2<'a> {
s1: &'a mut S1,
}
struct S3<'a> {
s2: &'a mut S2<'a>,
}
struct S4<'a> {
s3: &'a mut S3<'a>,
}
impl<'a> S4<'a> {
fn into_value(self) -> &'a mut i32 {
&mut self.s3.s2.s1.value
}
}
into_value 会消费 S4。返回值的生命周期标为 'a,对应嵌套引用指向的数据,而不是调用者手中的 S4。因此,调用者可以保留返回的引用,让 S4 随后离开作用域。如果实际结构体使用了不同的生命周期参数,返回类型需要根据真实的引用关系调整。Rust 仍会禁止通过同一条可变引用链创建重叠的可变借用。
如果要同时返回多个内部成员的可变引用,应在同一个方法中拆分互不重叠的字段,并在方法签名中明确表示这些引用共享的生命周期。如果结构体 4 还需要继续使用,就不能把同一条独占可变引用链同时交给调用者。此时可以一次性消费结构体 4,或只在它的借用范围内使用返回的引用。
备注:内容仅供参考。
