I am learning Rust. As I am a physicist, I am starting with a simple particles simulator, which is something I have previously done in C++. I wrote the following simple code:
#[derive(Debug)]
struct PhysicalVector {
x: f64,
y: f64,
z: f64,
}
impl PhysicalVector {
fn add(&mut self, other: &PhysicalVector) {
self.x = self.x + other.x;
self.y = self.y + other.y;
self.z = self.z + other.z;
}
}
#[derive(Debug)]
struct Particle {
position: PhysicalVector,
velocity: PhysicalVector,
}
fn main() {
let mut particle = Particle {
position: PhysicalVector {x: 0., y: 0., z: 0.},
velocity: PhysicalVector {x: 0., y: 0., z: 1.},
};
dbg!(&particle);
particle.position.add(&PhysicalVector{x: 1., y: 0., z: 0.}); // This is ok!
particle.position.add(&particle.velocity); // This is a mistake, you cannot add velocity and position!!
dbg!(&particle);
}
which works, but is prone to physics mistakes as it uses the same data type for position
and for velocity
, thus enabling to do things such as particle.position.add(&particle.velocity)
which is clearly something that should not compile under the laws of physics, and would it be nice to make it not compile in Rust as well.
The "brute force" solution is to replicate the definition of each different kind of vector, like this:
#[derive(Debug)]
struct Position {
x: f64,
y: f64,
z: f64,
}
impl Position {
fn add(&mut self, other: &Position) {
self.x = self.x + other.x;
self.y = self.y + other.y;
self.z = self.z + other.z;
}
}
#[derive(Debug)]
struct Velocity {
x: f64,
y: f64,
z: f64,
}
impl Velocity {
fn add(&mut self, other: &Velocity) {
self.x = self.x + other.x;
self.y = self.y + other.y;
self.z = self.z + other.z;
}
}
#[derive(Debug)]
struct Particle {
position: Position,
velocity: Velocity,
}
fn main() {
let mut particle = Particle {
position: Position {x: 0., y: 0., z: 0.},
velocity: Velocity {x: 0., y: 0., z: 1.},
};
dbg!(&particle);
particle.position.add(&Position{x: 1., y: 0., z: 0.}); // This is ok!
particle.position.add(&particle.velocity); // Now this does not compile, ok!
dbg!(&particle);
}
However, this is obviously not the way to go. What is the correct way of doing this in Rust? I mean, creating two data types Position
and Velocity
that are identical in all except in the data type?
I was suggested to proceed with generics, which look like C++ templates to me. I wrote this:
use std::marker::PhantomData;
#[derive(Debug)]
struct PhysicalVector<T> {
x: f64,
y: f64,
z: f64,
_t: PhantomData<T>,
}
impl<T> PhysicalVector<T> {
fn add(&mut self, other: &Self) {
self.x = self.x + other.x;
self.y = self.y + other.y;
self.z = self.z + other.z;
}
}
struct Position;
struct Velocity;
fn main() {
let pos: PhysicalVector<Position> = {x:0., y:0., z:0.};
}
However I cannot figure out how to instantiate one of these PhysicalVector<Position>
structures. How can I do this?