Ben A.
Ben A.

Reputation: 53

How to return a collection of cross-referenced data from a function?

In the following code I have a model, representing the state of a physical system. Within the model I would like to have two balls connected by a spring.

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),
        ],
    }
}

The definition for my Model is as follows:

struct Model {
    objs: Vec<Box<dyn PhysicsObject>>,
}

And the definitions for Spring and Spring::new() are:

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,
        }
    }
}

I am currently getting the following compiler error, due to (I belive) passing the balls references to the Spring, and then trying to return them (in a box) from the function.

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`

(I also get an identical error for ball2) How would I restructure my code in order to get the desired behavior? A spring needs to be able to update the balls it's attached to, and multiple spring should be able to be attached to one ball, which is currently impossible. I have a feeling this might be what an Rc is for, but I'm really no sure where to start with that. Thanks.

P.S. I am using nannou, a graphics library, which is where the App type comes from. However I do not think that's relevant to my issue.

P.P.S. Here's the definition for the PhysicsObject and the type Ball in case either are relevant.

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) { //The function that `Spring` calls
        self.acceleration += f / self.mass;
    }
}

Upvotes: 0

Views: 59

Answers (1)

cafce25
cafce25

Reputation: 27227

I am currently getting the following compiler error, due to (I belive) passing the balls references to the Spring, and then trying to return them (in a box) from the function.

That's exactly right. You'll have to come up with another way to refer to the Balls in the model.

One way to go about this would be to use indices into the Model.objs vector in the Springs but that way you can only access methods by the PyhsicalObject trait.

Another is to share ownership with Rc:

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)));
    let spring = Spring::new(ball1.clone(), ball2.clone(), 0.1);
    Model {
        objs: vec![
            Box::new(ball1),
            Box::new(ball2),
            Box::new(spring),
        ],
    }
}
impl<'a> Spring<'a> {
    pub fn new(b1: Rc<RefCell<Ball>>, b2: Rc<RefCell<Ball>>, _k: f32) -> Spring<'a>{
        Spring{
            target_length: (b1.borrow().position - b2.borrow().position).mag(),
            a: b1,
            b: b2,
        }
    }
    fn update(&self) {
        let f = v2(1.0, 0.0); // whatever force you need
        self.a.borrow_mut().applyForce(f);
        self.b.borrow_mut().applyForce(-f);
    }
}
impl PyhsicalObject for Rc<RefCell<Ball>> {
    //…
}

and adjust Ball to Rc<RefCell<Ball>> in most other places.

But using a lot of Rc<RefCell<T>> typically means you need to rethink your data model.

Upvotes: 1

Related Questions