嵌套向量中移动切片时的Rust借用检查器问题
嵌套向量间的切片移动问题(Rust)
问题场景
当用单个向量模拟栈时,使用extend_from_slice可以高效地将一个栈的切片移动到另一个栈,无需复制数据,示例代码可正常编译运行:
fn main() { let mut s1 = vec![1, 2, 3]; let mut s2 = vec![]; println!("before #{}: {:?}", 1, s1); println!("before #{}: {:?}", 2, s2); s2.extend_from_slice(&s1[1..]); s1.truncate(1); println!("after #{}: {:?}", 1, s1); println!("after #{}: {:?}", 2, s2); }
但如果将多个栈存储为嵌套向量(Vec<Vec<T>>),尝试将一个内层向量的切片追加到另一个内层向量时,会触发Rust借用检查器的错误:
fn main() { let mut stacks = vec![vec![1, 2, 3], vec![2, 3, 4], vec![]]; for (n, stack) in stacks.iter().enumerate() { println!("before #{}: {:?}", n, stack); } stacks[2].extend_from_slice(&stacks[1][1..]); stacks[1].truncate(1); for (n, stack) in stacks.iter().enumerate() { println!("after #{}: {:?}", n, stack); } }
错误信息:
error[E0502]: cannot borrow `stacks` as immutable because it is also borrowed as mutable --> examples/vector_vector_move.rs:10:34 | 10 | stacks[2].extend_from_slice(&stacks[1][1..]); | ------ ----------------- ^^^^^^ immutable borrow occurs here | | | | | mutable borrow later used by call | mutable borrow occurs here
逻辑上操作的是两个独立的内层向量,顶层向量无需改变大小,不存在安全问题,但编译器无法识别不同索引对应的内层向量是分离的,因此判定为同时存在可变和不可变借用冲突。
解决方案
方法1:用split_at_mut拆分可变引用
split_at_mut可以将外层向量拆分为两个不重叠的可变切片,让编译器明确两个内层向量的引用没有重叠,从而允许同时借用:
fn main() { let mut stacks = vec![vec![1, 2, 3], vec![2, 3, 4], vec![]]; for (n, stack) in stacks.iter().enumerate() { println!("before #{}: {:?}", n, stack); } // 将外层向量拆分为[0..2]和[2..]两个可变切片 let (left, right) = stacks.split_at_mut(2); let source = &mut left[1]; let dest = &mut right[0]; dest.extend_from_slice(&source[1..]); source.truncate(1); for (n, stack) in stacks.iter().enumerate() { println!("after #{}: {:?}", n, stack); } }
方法2:使用unsafe代码(仅在必要时使用)
如果源和目标的索引是动态计算的,split_at_mut难以处理,可以手动使用unsafe获取两个可变引用,但必须确保两个索引不指向同一个内层向量,否则会导致未定义行为:
fn main() { let mut stacks = vec![vec![1, 2, 3], vec![2, 3, 4], vec![]]; for (n, stack) in stacks.iter().enumerate() { println!("before #{}: {:?}", n, stack); } let src_idx = 1; let dest_idx = 2; // 必须确保两个索引不同,避免未定义行为 assert_ne!(src_idx, dest_idx); unsafe { let source = stacks.get_unchecked_mut(src_idx); let dest = stacks.get_unchecked_mut(dest_idx); dest.extend_from_slice(&source[1..]); source.truncate(1); } for (n, stack) in stacks.iter().enumerate() { println!("after #{}: {:?}", n, stack); } }
方法3:重组数据结构(用Box<Vec<T>>存储内层向量)
将每个内层向量封装到Box中,外层向量存储Box<Vec<T>>。由于Box的指针是稳定的,编译器可以识别不同Box指向的内存区域相互独立,从而避免借用冲突:
fn main() { let mut stacks = vec![Box::new(vec![1, 2, 3]), Box::new(vec![2, 3, 4]), Box::new(vec![])]; for (n, stack) in stacks.iter().enumerate() { println!("before #{}: {:?}", n, stack); } let source = &mut stacks[1]; let dest = &mut stacks[2]; dest.extend_from_slice(&source[1..]); source.truncate(1); for (n, stack) in stacks.iter().enumerate() { println!("after #{}: {:?}", n, stack); } }
内容的提问来源于stack exchange,提问作者Mats Kindahl
相关产品推荐
相关产品推荐

