如何解决Rust中同一对象无法拥有两个可变引用的借用检查问题?
我正在移植Constructive Solid Geometry (CSG)库来学习Rust,目前被借用检查器的「同一对象不能拥有两个可变引用」规则卡住了。我实现了一个网格切割函数,类Rust伪代码如下:
fn cut( mesh: &Vec<Polygon>, cutting_plane: &Plane, front: &mut Vec<Polygon>, back: &mut Vec<Polygon>, coplanar_front: &mut Vec<Polygon>, coplanar_back: &mut Vec<Polygon>) { for polygon in mesh.iter() { let polygon_position = calculate_polygon_position_in_relation_to_plane(&polygon, &cutting_plane); match polygon_position { InFront => { front.push(polygon.clone()); } InBack => { back.push(polygon.clone()); } Coplanar => { if polygon is coplanar with cutting_plane but still a little bit in the front { coplanar_front.push(polygon.clone()); } else { coplanar_back.push(polygon.clone()); } } Spanning => { let (front_polygon, back_polygon) = cut_polygon(&polygon, &cutting_plane); front.push(front_polygon); back.push(back_polygon); } } } }
这个函数的结果依赖传入的4个Vec参数(front、back、coplanar_front、coplanar_back)的填充情况。我需要两种调用方式:
第一种方式:关心共面多边形的具体位置
/* ... 初始化 some_mesh 和 some_cutting_plane ... */ let mut front = Vec::new(); let mut back = Vec::new(); let mut coplanar_front = Vec::new(); let mut coplanar_back = Vec::new(); cut(&some_mesh, &some_cutting_plane, &mut front, &mut back, &mut coplanar_front, &mut coplanar_back); /* ... 使用 coplanar_front、coplanar_back、front 和 back 向量 */
第二种方式:不关心共面多边形的具体位置,想把共面的都放到同一个Vec里,但借用检查器会阻止这种调用,因为同一对象不能有两个可变引用
/* ... 初始化 some_mesh 和 some_cutting_plane ... */ let mut front = Vec::new(); let mut back = Vec::new(); let mut coplanar = Vec::new(); // 这里会被借用检查器阻止 cut(&some_mesh, &some_cutting_plane, &mut front, &mut back, &mut coplanar, &mut coplanar); /* ... 使用 coplanar、front 和 back 向量 */
我不想用创建临时数组再合并的朴素方案(会产生不必要的拷贝),也不想写两个大部分代码重复的函数。我知道Box这类结构可以用于多可变引用,但不知道正确用法。请问Rust里有没有合适的特性或更优的解决思路?
方法1:使用&mut dyn Extend<Polygon>参数实现灵活扩展
将coplanar_front和coplanar_back的参数类型从&mut Vec<Polygon>改为&mut dyn Extend<Polygon>,这样可以接收任何支持扩展元素的类型,包括原生Vec,或者自定义的合并适配器,完美兼容两种调用场景。
首先修改cut函数的签名与内部逻辑:
fn cut( mesh: &[Polygon], // 改用&[Polygon]比&Vec<Polygon>更通用 cutting_plane: &Plane, front: &mut Vec<Polygon>, back: &mut Vec<Polygon>, coplanar_front: &mut dyn Extend<Polygon>, coplanar_back: &mut dyn Extend<Polygon>, ) { for polygon in mesh { let polygon_position = calculate_polygon_position_in_relation_to_plane(polygon, cutting_plane); match polygon_position { InFront => front.push(polygon.clone()), InBack => back.push(polygon.clone()), Coplanar => { if polygon_is_coplanar_front(polygon, cutting_plane) { coplanar_front.extend_one(polygon.clone()); } else { coplanar_back.extend_one(polygon.clone()); } } Spanning => { let (front_poly, back_poly) = cut_polygon(polygon, cutting_plane); front.push(front_poly); back.push(back_poly); } } } }
然后定义一个简单的适配器,用于将两个分支的共面多边形合并到同一个Vec:
struct MergeExtend<'a>(&'a mut Vec<Polygon>); impl<'a> Extend<Polygon> for MergeExtend<'a> { fn extend_one(&mut self, item: Polygon) { self.0.push(item); } fn extend<T: IntoIterator<Item = Polygon>>(&mut self, iter: T) { self.0.extend(iter); } }
现在两种调用方式都能正常工作:
// 场景1:分开存储共面多边形 let mut front = Vec::new(); let mut back = Vec::new(); let mut coplanar_front = Vec::new(); let mut coplanar_back = Vec::new(); cut(&some_mesh, &some_cutting_plane, &mut front, &mut back, &mut coplanar_front, &mut coplanar_back);
// 场景2:合并共面多边形到同一个Vec let mut front = Vec::new(); let mut back = Vec::new(); let mut coplanar = Vec::new(); cut( &some_mesh, &some_cutting_plane, &mut front, &mut back, &mut MergeExtend(&mut coplanar), &mut MergeExtend(&mut coplanar), );
这种方案无额外拷贝,也不需要重复编写函数逻辑,是最优雅的解决方式。
方法2:使用RefCell绕开编译期借用检查
如果不想大幅修改函数签名,可以用RefCell将编译期的借用检查延迟到运行时。由于你的场景中,coplanar_front和coplanar_back的写入分支是互斥的(同一个多边形只会进入其中一个分支),因此不会触发运行时panic。
调用代码示例:
use std::cell::RefCell; let mut front = Vec::new(); let mut back = Vec::new(); let coplanar = RefCell::new(Vec::new()); cut( &some_mesh, &some_cutting_plane, &mut front, &mut back, &mut coplanar.borrow_mut(), &mut coplanar.borrow_mut(), );
这种方式有轻微的运行时开销,且依赖业务逻辑的互斥保证,适合快速适配现有代码的场景。
方法3:重构为返回自定义结果结构体
将cut函数改为返回包含所有结果的结构体,再提供合并共面多边形的方法,兼顾两种使用场景:
struct CutResult { front: Vec<Polygon>, back: Vec<Polygon>, coplanar_front: Vec<Polygon>, coplanar_back: Vec<Polygon>, } impl CutResult { // 合并共面多边形的方法 fn merged_coplanar(self) -> (Vec<Polygon>, Vec<Polygon>, Vec<Polygon>) { let mut coplanar = self.coplanar_front; coplanar.extend(self.coplanar_back); (self.front, self.back, coplanar) } } fn cut(mesh: &[Polygon], cutting_plane: &Plane) -> CutResult { let mut front = Vec::new(); let mut back = Vec::new(); let mut coplanar_front = Vec::new(); let mut coplanar_back = Vec::new(); // 原函数的填充逻辑 CutResult { front, back, coplanar_front, coplanar_back, } }
调用方式:
// 场景1:使用分开的共面多边形 let result = cut(&some_mesh, &some_cutting_plane); // 直接使用 result.front、result.back、result.coplanar_front、result.coplanar_back
// 场景2:使用合并后的共面多边形 let (front, back, coplanar) = cut(&some_mesh, &some_cutting_plane).merged_coplanar();
这种方案的extend操作开销极低(Vec会直接移动内存块而非逐元素拷贝),且代码结构更清晰,适合追求可读性的场景。
内容的提问来源于stack exchange,提问作者Akufishi

