You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何从函数返回带交叉引用的数据集合?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.

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.11 12:25:21