I'm learning rust with mostly a background in managed languages. I have a little C background.
As a learning project I am re-implementing a linear-transformations library I've been working on in C#.
This is my first reusable library I've tried to make in Rust and I'd like feedback on creating and using modules, as well as rust programming in general.
Project structure
+ transforms
+ src
- lib.rs
- tests.rs
- transforms.rs
- vectors.rs
+ target
- Cargo.toml
I haven't made any changes to the automatic cargo.toml
.
transforms.rs
is empty and have the linear transformation code added to it in the future.
lib.rs
pub mod vectors;
mod tests; // tests don't get run if I don't have this, not sure why?
vectors.rs
use std::fmt;
pub struct KVector3{
pub x : f64,
pub y : f64,
pub z : f64,
}
pub trait Vector {
fn magnitude_squared(&self) -> f64;
fn magnitude(&self) -> f64;
}
pub trait Vector3 {
fn dot(&self, v : KVector3) -> f64;
fn cross(&self, v : KVector3) -> KVector3;
fn zero() -> KVector3;
fn i_hat() -> KVector3;
fn j_hat() -> KVector3;
fn k_hat() -> KVector3;
}
impl Vector for KVector3 {
fn magnitude_squared(&self) -> f64 {
self.x * self.x + self.y * self.y + self.z * self.z
}
fn magnitude(&self) -> f64 {
(self.x * self.x + self.y * self.y + self.z * self.z).sqrt()
}
}
impl Vector3 for KVector3 {
fn dot(&self, v : KVector3) -> f64 {
self.x * v.x + self.y * v.y + self.z + v.z
}
fn cross(&self, v : KVector3) -> KVector3 {
KVector3 {
x : self.y * v.z - self.z * v.y,
y : self.x * v.z - self.z * v.x,
z : self.x * v.y - self.y * v.x
}
}
fn zero() -> KVector3 { KVector3 { x: 0., y: 0., z: 0. } }
fn i_hat() -> KVector3 { KVector3 { x: 1., y: 0., z: 0. } }
fn j_hat() -> KVector3 { KVector3 { x: 0., y: 1., z: 0. } }
fn k_hat() -> KVector3 { KVector3 { x: 0., y: 0., z: 1. } }
}
impl PartialEq<KVector3> for KVector3 {
fn eq(&self, other : &KVector3) -> bool {
self.x == other.x && self.y == other.y && self.z == other.z
}
}
impl fmt::Debug for KVector3 {
fn fmt(&self, f : &mut fmt::Formatter) -> fmt::Result {
write!(f, "<{},{},{}>", self.x, self.y, self.z)
}
}
tests.rs
#[cfg(test)]
mod tests {
use ::vectors::{Vector, Vector3, KVector3};
#[test]
fn unit_magnitude_squared() {
let mag_squared = KVector3::i_hat().magnitude_squared();
assert_eq!(1., mag_squared);
}
#[test]
fn v3_magnitude_squared() {
let triangle = KVector3 { x : 3., y : 4., z: 0. };
let mag_squared = triangle.magnitude_squared();
assert_eq!(25., mag_squared);
}
#[test]
fn v3_magnitude() {
let triangle = KVector3 { x : 3., y : 4., z: 0. };
let mag = triangle.magnitude();
assert_eq!(5., mag);
}
#[test]
fn cross_product() {
let product = KVector3::i_hat().cross(KVector3::j_hat());
assert_eq!(KVector3::k_hat(), product);
}
}
-
1\$\begingroup\$ Welcome to Code Review! I changed the title so that it is more specific about this code. Feel free to edit and give it a different title if there is something more appropriate. \$\endgroup\$Sᴀᴍ Onᴇᴌᴀ– Sᴀᴍ Onᴇᴌᴀ ♦2018年05月16日 20:56:50 +00:00Commented May 16, 2018 at 20:56
1 Answer 1
Unit tests should be put in the file they're testing
It's recommended to put the unit tests into the same file as the functions you're testing. This prevents your code from getting out of sync with your tests more easily. Also, you don't want one single large tests.rs
for all your unit tests.
Use proper types in traits
At the moment, as soon as we use any function from Vector3
, we end up with a KVector3
:
pub trait Vector3 {
fn dot(&self, v : KVector3) -> f64;
fn cross(&self, v : KVector3) -> KVector3; // here
fn zero() -> KVector3; // here
fn i_hat() -> KVector3; // here
fn j_hat() -> KVector3; // here
fn k_hat() -> KVector3; // here
}
That's probably not what you intended. Use Self
instead here. Furthermore, all your functions are fixed to f64
, but one can implement all functions from Vector3
for any number type. The following interface encapsulates that:
pub trait Vector3 {
type Output;
fn dot(&self, v: Self) -> Self::Output;
fn cross(&self, v: Self) -> Self;
fn zero() -> Self;
fn i_hat() -> Self;
fn j_hat() -> Self;
fn k_hat() -> Self;
}
Use derive
for canonical implementations
If you use #[derive(PartialEq, Debug)]
you don't have to implement both variants yourself.
Consider generics for your structs (and traits)
Your KVector3
only supports f64
. However, we can imagine a situation where we want to store f32
, for example for GPU calculation. We can use KVector3
also in that circumstance if we make it generic.
Add documentation and examples
Since you're working on a reusable library you want to have some documentation at hand. Furthermore, examples in your documentation are automatically checked with cargo test
.
Explore related questions
See similar questions with these tags.