如何从函数返回带交叉引用的数据集合?Rust代码重构求助
问题描述
我想用Rust实现两个由弹簧连接的小球物理系统,当前代码及遇到的问题如下:
核心代码实现
模型初始化函数
fn model(_app: &App) -> Model { let mut ball1 = Ball::new(-250.0, 0.0, 50.0); let mut ball2 = Ball::new(250.0, 0.0, 50.0); let spring = Spring::new(&mut ball1, &mut ball2, 0.1); Model { objs: vec![ Box::new(ball1), Box::new(ball2), Box::new(spring), ], } }
类型定义
struct Model { objs: Vec<Box<dyn PhysicsObject>>, } pub trait PhysicsObject { fn update(&mut self, dt: f32); fn show(&self, draw: &Draw); } pub struct Ball { pub color: Rgb<f64>, pub radius: f32, pub position: V2, pub velocity: V2, pub acceleration: V2, pub mass: f32, } impl Ball { pub fn new(x: f32, y: f32, r: f32) -> Ball { Ball { color: rgb(0.0, 0.0, 0.0), radius: r, position: v2(x, y), velocity: V2::zero(), acceleration: V2::zero(), mass: 1.0, } } pub fn applyForce(&mut self, f: V2) { self.acceleration += f / self.mass; } } pub struct Spring<'a> { pub target_length: f32, pub a: &'a mut Ball, pub b: &'a mut Ball, } impl<'a> Spring<'a> { pub fn new(b1: &'a mut Ball, b2: &'a mut Ball, k: f32) -> Spring<'a>{ Spring{ target_length: (b1.position - b2.position).mag(), a: b1, b: b2, } } }
编译器错误
error[E0505]: cannot move out of `ball1` because it is borrowed --> src/main.rs:21:22 | 18 | let spring = Spring::new(&mut ball1, &mut ball2, 0.1); | ---------- borrow of `ball1` occurs here ... 21 | Box::new(ball1), | ^^^^^ move out of `ball1` occurs here 22 | Box::new(ball2), 23 | Box::new(spring), | ---------------- cast requires that `ball1` is borrowed for `'static`
ball2会出现完全相同的错误。需要重构代码实现:
- 弹簧能够更新连接的小球
- 单个小球可连接多个弹簧
解决方案
问题根源是可变引用的生命周期冲突:Spring持有了Ball的可变引用,但同时又要把Ball移动到Box中,违反了Rust的借用规则。要实现共享可变访问,需要用Rc<RefCell<Ball>>(引用计数+内部可变性),具体步骤如下:
1. 调整Spring结构体定义
去掉生命周期参数,改用Rc<RefCell<Ball>>存储小球引用,同时补充弹性系数k的存储:
use std::cell::RefCell; use std::rc::Rc; pub struct Spring { pub target_length: f32, pub k: f32, pub a: Rc<RefCell<Ball>>, pub b: Rc<RefCell<Ball>>, } impl Spring { pub fn new(b1: Rc<RefCell<Ball>>, b2: Rc<RefCell<Ball>>, k: f32) -> Spring { let target_length = (b1.borrow().position - b2.borrow().position).mag(); Spring { target_length, k, a: b1, b: b2, } } }
2. 为包装后的Ball实现PhysicsObject trait
通过borrow_mut()获取可变引用,完成物理更新和渲染逻辑:
impl PhysicsObject for Rc<RefCell<Ball>> { fn update(&mut self, dt: f32) { let mut ball = self.borrow_mut(); // 标准物理运动更新 ball.velocity += ball.acceleration * dt; ball.position += ball.velocity * dt; ball.acceleration = V2::zero(); } fn show(&self, draw: &Draw) { let ball = self.borrow(); draw.ellipse() .xy(ball.position) .radius(ball.radius) .color(ball.color); } }
3. 为Spring实现PhysicsObject trait
计算弹簧弹力并应用到两个小球,同时实现渲染逻辑:
impl PhysicsObject for Spring { fn update(&mut self, dt: f32) { let mut a = self.a.borrow_mut(); let mut b = self.b.borrow_mut(); // 计算弹簧当前长度与形变 let delta = b.position - a.position; let current_length = delta.mag(); let force_magnitude = self.k * (current_length - self.target_length); let force = delta.normalize() * force_magnitude; // 对两个球施加大小相等、方向相反的力 a.applyForce(-force); b.applyForce(force); } fn show(&self, draw: &Draw) { let a = self.a.borrow(); let b = self.b.borrow(); draw.line() .start(a.position) .end(b.position) .color(rgb(0.5, 0.5, 0.5)) .weight(2.0); } }
4. 修改Model初始化逻辑
通过Rc::clone()共享Ball实例,避免所有权冲突:
fn model(_app: &App) -> Model { let ball1 = Rc::new(RefCell::new(Ball::new(-250.0, 0.0, 50.0))); let ball2 = Rc::new(RefCell::new(Ball::new(250.0, 0.0, 50.0))); // 克隆Rc仅增加引用计数,不复制Ball实例 let spring = Spring::new(Rc::clone(&ball1), Rc::clone(&ball2), 0.1); Model { objs: vec![ Box::new(Rc::clone(&ball1)), Box::new(Rc::clone(&ball2)), Box::new(spring), ], } }
5. 扩展:单个小球连接多个弹簧
只需多次克隆对应的Rc<RefCell<Ball>>即可实现,示例:
// 给ball1新增一个连接到固定点的弹簧 let fixed_point = Rc::new(RefCell::new(FixedPoint::new(0.0, 0.0))); let spring2 = Spring::new(Rc::clone(&ball1), fixed_point, 0.05); model.objs.push(Box::new(spring2));
内容的提问来源于stack exchange,提问作者Ben A.
相关产品推荐
相关产品推荐

