1
\$\begingroup\$

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);

    }
}
\$\endgroup\$
  • 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ᴇᴌᴀ May 16 '18 at 20:56
1
\$\begingroup\$

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.rsfor 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.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.