Rust’s Copy trait – An example of a Vec inside a struct
While implementing a very primitive molecular dynamics simulator from scratch in Rust, I have encountered an interesting corner case I believe is worth sharing with anyone learning Rust.
Among other artifacts, I have set up a primitive model class for storing some information about a single Particle in a file particle.rs
:
#[derive(Debug, Copy, Clone)] pub enum SubatomicParticleType { Proton = 1, Neutron = 2, Electron = 3, } #[derive(Debug, Copy, Clone)] pub struct Particle { pub pos: Vector3<f64>, // current Euclidean XYZ position [m] pub v: Vector3<f64>, // current Euclidean XYZ accelleration [m/s] pub a: Vector3<f64>, // current Euclidean XYZ accelleration [m/s^2] pub m: f64, // particle mass [kg] pub r: f64, // radius of the particle [m] pub e: f64, // potential energy [kcal/mol] pub q: f64, // electrical charge [C] pub particle_type: SubatomicParticleType, }
Nothing fancy, just some basic properties like position, velocity, mass, charge, etc. Besides that, in a file atom.rs
I have a basic definition of a single atom (nucleus + electrons which orbit it) and a method to create hydrogen atom:
#[derive(Debug)] pub struct Atom { pub electrons: Vec<particle::Particle>, // nucleus is represented as a single particle with a charge // equal to the atomic number * charge of a proton pub nucleus: particle::Particle, } impl Atom { pub fn create_hidrogen(location: Vector3<f64>) -> Self { // According to: https://www.sciencefocus.com/science/whats-the-distance-from-a-nucleus-to-an-electron/ // the electron (if it was a particle, hehe) orbits the nucleus at a distance of 1/20 nanometers let offset = Vector3::new(0.05e-9, 0.0, 0.0); let electron = particle::Particle::create_electron(location + offset); Self { nucleus: particle::Particle::create_proton(location), electrons: vec![electron], } } }
The main simulation controller is implemented in file simulation.rs
:
pub struct Simulation { delta_t: f64, pub particles: Vec<particle::Particle>, temperature: f64, } impl Simulation { // deltaT - simulation timestamp [fs] pub fn new(delta_t: f64, temperature: f64) -> Self { Simulation { delta_t: delta_t, particles: Vec::new(), temperature: temperature, } } pub fn step(self: &mut Self) { ... } pub fn add_atom(self: &mut Self, atom: &atom::Atom) { for particle in &atom.electrons { self.particles.push(*particle); } self.particles.push(atom.nucleus); } }
This code compiles without errors.
Now, let’s focus on the add_atom
function. Notice that de-referencing of *particle
when adding it to the self.particles
vector? Ugly, right?
At first I wanted to avoid references altogether, so my C++ mindset went something like this:
pub fn add_atom(self: &mut Self, atom: &atom::Atom) { for particle in atom.electrons { self.particles.push(particle); } self.particles.push(atom.nucleus); }
The error I got after trying to compile this was:

So, what’s happening here? I was trying to iterate over electrons in a provided atom by directly accessing the value of a member property electrons
of an instance atom
of type &atom::Atom
. There are two ways my loop can get the value of the vector behind that property: moving the ownership or copying it. As the brilliant Rust compiler correctly pointed out, this property doesn’t implement Copy
trait (since it’s a Vec<T>), so copying is not possible. The only remaining way to get a value behind it is to move the ownership from a function parameter into a temporary loop variable.
Now, this isn’t possible either because you can’t move ownership of something behind a shared reference. This is why I’ve been left with the ugly de-referencing shown in the first place. Besides, I had to mark Particle
with Copy
and Clone
traits as well.
Expanding a struct with a Vec<T> member property
In order to record historical data for plotting purposes about a particle’s trajectory through space, forces acting on it, its velocities, etc. I wanted to add a HashMap
of vectors to the Particle
struct, so the string keys represent various properties I need the history for. So, my Particles
struct looked something like this:

Rust didn’t like this new HashMap
of vectors due to the reason we already went over above – vectors can’t implement Copy
traits. If I really wanted to keep this property the way it is, I would have to remove the Copy
trait from the Particle
struct. As you may already assume, this lead to another issue, this time in simulation.rs
:

By removing the Copy
trait on Particle
struct we removed the capability for it to be moved by de-referencing. Since we must provide ownership to the each element of the vector self.particles
, the only option is to clone each element explicitly before pushing it to the vector:

This code will finally compile and do what I need it to do. Yaaaay!
In cases like this Rust’s borrow checker can be described as annoying at first, but it does force you as a developer to take care of the underlying memory on time. For instance, de-referencing a pointer in C++ will almost never stop you from compiling, but you have to pray to the Runtime Gods nothing goes wrong. Rust, on the other hand, will force you to think about is it possible to de-reference this without any issues in all of the cases or not, and if not it will scream at you until you change your approach about it. In the example above I had to accept the fact my particle
will be cloned physically instead of just getting a quick and dirty access to it through a reference, which is great.
Fighting the compiler can get rough at times, but at the end of the day the overhead you pay is a very low price for all of the runtime guarantees.