What is the good rustian way to design the algorithm and the data types for this simulation?

After falling in love with rust, i decided to rewrite an old particle simulation, originally written in Julia, in Rust.
However, I'm still learning the language and don't have much experience how to organise a Rust program to optimise for performance.

Basically, the simulations that I want to write consist of two structs

pub struct Particle {
    pub id: i32,
    pub position: i32
    pub particle_buffer_one: HashMap<i32, f32>
    pub particle_buffer_two: HashMap<i32, f32>
    // Plus several fields that are collections, e.g vector, hashmap
    // that will act as buffers to save different types of data
    // belonging to individual instance of this class
}

pub struct World {
    pub id: i32,
    pub particles_n: i32,
    pub linear_size: i32,
    pub set_particles: HashMap<i32, Particle>
    pub world_buffer_one: HashMap<i32, f32>
    pub world_buffer_two: HashMap<i32, f32>
    // Plus several fields that are collections
    // that will act as buffers
}

And in the main(), I first create an instance of World that consist of N number of Particle instances (see set_particle field of the class World). Then, the body of the main contains a loop in the following format

    let s_world = World::new(N)
    for i in 0..1e+12 {
    // choose one of instance of `Particle` from `set_particle` of `s_world`
    // change the value of `particle_buffer_one` at key `i`
    // change the value of `particle_buffer_two` at key `i`

    // change the value of `world_buffer_one` at key `i`
    // change the value of `world_buffer_two` at key `i`
    }

I'm a fan of immutable objects in general, simply because it is safer/simpler to deal with. However, given that I need to get the best performance from this code in terms of speed, here are some design questions that I don't know how to answer:

  1. For the buffers, should I use hashmaps. Are these the fastest?
  2. Is there a way to use immutable instances of World and Particle structs?
  3. Would using mutable instances of these structs be faster?

Regarding the take that “immutable objects are simpler to deal with”, in Rust that argument against mutability really only applies to interior mutability (so types like Cell/RefCell or Mutex/RwLock).

Rust’s normal mutability comes hand in hand with exclusive access which means there are not really any downsides in terms of safety&simplicity of reasoning about the effects of mutation.

Of course, if your algorithm fails to work with Rust’s ordinary mutability – or if you need the benefit of immutable data structures that you can keep history better, i.e. have persistent views of older states of the data structure – then considering immutable data structures can still have a point. The standard library’s types like Vec or HashMap aren’t really made for this though. (I’m not sure off the top of my head what the best crate(s) for persistent data structures would be.)

Given the main loop’s structure you posted so far, I do however see no issues you could encounter if using ordinary mutable references in Rust to access (and modify) the data you need to modify.


The usage of HashMap here is a bit hard to judge if you don’t share what the meaning of these i32 keys is. Your main loop you’re outlining suggests that you use values i in 0..1e+12 for those keys, which seems almost as if your indices are just contiguous and non-negative – if that’s the case, you might be able to just work with Vecs indexed by the normal (positional) indexing. However, maybe I’m misinterpreting your description… really there isn’t so much context being given.

On the topic of me trying to understanding the meaning of i32 values in your code, I’m also not sure what the ids are used for, or what particles_n/linear_size/position mean (and if all of those can sensibly ever have negative values).

2 Likes

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.