Learning Rust : Solar Systems

Everything is implicit and I don't know the exact types I'm working with.
< Home >

So, now that I have a struct for defining orbitals, I can begin work on a container for these orbitals. I can store the orbitals in a vector, and iterate through the list of orbitals to tick and update them for a given time. We start with the following concept code:


struct System
{
    orbitals : Vec<Orbital>
}

impl System
{
    pub fn tick(&self, t : Second)
    {
        let orbitals_len = self.orbitals.len();
        for i in 0..orbitals_len {
            let orbital = self.orbitals[i];
            orbital.tick(self, t);
        }
    }
}
            

This simple code leads me to my first major hurdle when dealing with Rust: Borrows against a container take the entire object, not just the element at a given index. Because of this quirk, I cannot borrow the parent orbital to calculate parts of my orbital state vectors. Since my "tick" function needs the parent orbital to calculate the gravitational parameter. Here's the code that fuels this problem:


pub fn tick(&mut self, system: &System) -> ()
{
    ...
    let origin_index = self.origin.unwrap();
    let origin_orbital = system.orbitals[origin_index];
    let origin_mass = origin_orbital.mass;
    ...
}
            

Because I can't borrow from the orbital vector more than once, I can't get the mass of the origin orbital.

Just to be clear, I understand why the borrow checker takes the entire vector when taking an element. If the element gets deleted or changed or the vector gets added to, then any other reference to the vector's elements must be re-evaluated. To properly borrow multiple mutable elements, I have to somehow split the vector in such a way that both the origin and ticking orbital are in seperate arrays.

Based on this information, and some help from the internet, here is what I came up with for accessing the parent-orbital pair


fn orbital_set(&self, orbitals : &mut [Orbital], i : usize)
    -> (&mut Orbital, Option<&mut Orbital>)
{
    //The orbital vector must first be converted to a mutable span, so that I can access the raw data.
    assert!(orbitals.len() < i);
    unsafe {
        //From the orbital span, take a raw pointer from the given index, cast to a pointer to a mutable Orbital, and dereference.
        let orbital = &mut *(orbitals.get_unchecked_mut(i) as *mut Orbital);
        //Assuming the orbital is a valid object, check for the parent orbital.
        match orbital.origin() {
            Some(origin_i) => {
                //Make sure the origin index does not break rules.
                assert!(origin_i != i, "Orbital cannot orbit itself");
                assert!(origin_i < orbitals.len());
                let origin = &mut *(orbitals.get_unchecked_mut(i) as *mut Orbital)
                (orbital, Some(origin))
            },
            None => (orbital, None)
        }
    }
}
            

I hate the unsafe block, but this is the only solution that I could find that had the characteristics that I wanted. Applied to the global tick method it looks like this:


pub fn tick(&mut self) -> ()
{
    let orbitals = &mut self.orbitals.borrow_mut()[..];
    for i in 0..orbitals.len() {
        let orbital_set = self.orbital_set(orbitals, i);
        match orbital_set.1 {
            Some(origin) => { orbital_set.0.tick(origin, t); }
            None => {}
        }
    }
}
            

where the orbital tick method looks like:


pub fn tick(&mut self, origin : &mut Orbital, t : Second)
{
    ...
    let origin_mass = origin.mass;
    ...
}
            

Now the challenge will be drawing all of this information on the screen!