为何Rust中多次调用borrow_mut_two会触发可变借用编译错误?
可单次正常运行的函数
我编写了一个从向量中借用两个独立元素的函数borrow_mut_two,单次调用时可以正常编译并运行:
fn borrow_mut_two<T>(v: &mut [T], i: usize, j: usize) -> (&mut T, &mut T) { assert!(i < j); let (left, right) = v.split_at_mut(j); (&mut left[i], &mut right[0]) } fn test() { let mut v = vec![0, 1, 2, 3, 4, 5, 6, 7, 8]; let i = 2; let j = 5; let (ref_a, ref_b) = borrow_mut_two(&mut v, i, j); *ref_a += 1; *ref_b += 5; assert_eq!(*ref_a, i + 1); assert_eq!(*ref_b, j + 5); }
第二次调用编译失败
但在test函数中第二次调用borrow_mut_two时,代码无法通过编译:
fn test() { let mut v = vec![0, 1, 2, 3, 4, 5, 6, 7, 8]; let i = 2; let j = 5; let (ref_a, ref_b) = borrow_mut_two(&mut v, i, j); // 新增的第二次调用 let (other_ref_a, other_ref_b) = borrow_mut_two(&mut v, i, j); *ref_a += 1; *ref_b += 5; assert_eq!(*ref_a, i + 1); assert_eq!(*ref_b, j + 5); }
编译器报错显示存在两次可变借用冲突:
229 | let (ref_a, ref_b) = borrow_mut_two(&mut v, i, j); | ------ first mutable borrow occurs here 230 | let (other_ref_a, other_ref_b) = borrow_mut_two(&mut v, i, j); | ^^^^^^ second mutable borrow occurs here
猜想与疑问
我猜想这是因为Rust的生命周期省略规则自动为函数添加了生命周期参数,使得函数签名等价于:
fn borrow_mut_two<'a, T>(v: &'a mut [T], i: usize, j: usize) -> (&'a mut T, &'a mut T)
导致第一次调用的可变借用生命周期未结束时,第二次调用再次借用同一个向量,从而触发冲突。我想确认该猜想是否正确,同时疑问Rust的第二条生命周期省略规则是否适用于元组中的每个元素?
猜想确认:完全正确
你的推断是对的。Rust的生命周期省略规则中,当函数只有一个输入生命周期参数时(这里就是&'a mut [T]的'a),所有返回的引用类型都会被自动赋予这个生命周期。因此borrow_mut_two返回的两个&mut T都会绑定到输入向量的生命周期'a。
第一次调用后,ref_a和ref_b会一直持有对v的可变借用直到它们被销毁(也就是test函数结束),这段时间内v处于被可变借用的状态,第二次调用尝试再次可变借用同一个向量,自然违反了Rust的“可变引用独占”规则,导致编译报错。
生命周期省略规则对元组的适用性
第二条生命周期省略规则完全适用于元组中的每个元素。规则核心是:只要函数只有一个输入生命周期参数,所有返回的引用(不管是单个引用,还是元组、结构体等复合类型内部的引用)都会共享这个输入生命周期。所以你的返回元组里的两个&mut T都会被推断为'a,这正是问题的核心原因。
解决方法
如果需要允许多次调用borrow_mut_two,最简单的方式是让第一次的借用提前结束。可以把第一次调用的操作放在一个代码块中,当代码块结束时,ref_a和ref_b会被销毁,对应的可变借用也会被释放:
fn test() { let mut v = vec![0, 1, 2, 3, 4, 5, 6, 7, 8]; let i = 2; let j = 5; // 用代码块限制第一次借用的生命周期 { let (ref_a, ref_b) = borrow_mut_two(&mut v, i, j); *ref_a += 1; *ref_b += 5; assert_eq!(*ref_a, i + 1); assert_eq!(*ref_b, j + 5); } // 第一次借用已释放,可以正常第二次调用 let (other_ref_a, other_ref_b) = borrow_mut_two(&mut v, i, j); // 后续对other_ref_a和other_ref_b的操作 }
如果需要同时持有多组不重叠的元素借用,Rust的安全类型系统无法直接做到(因为编译器无法静态证明不同调用的索引不重叠),这种情况下可以考虑使用unsafe代码手动实现,但需要自己保证索引的安全性,避免出现数据竞争或悬垂引用。
内容的提问来源于stack exchange,提问作者user4757175

