1
\$\begingroup\$

Hi I am trying to get into Rust by implementing a small library for vector calculations.

I am mainly asking to point out whether I chose a valid approach. I also am interested in comments on the overall details I might have missed or got the wrong way.

So for my Vec3 implementation I basically declared a custom type which is an array of length 3. Afterwards I am simply implementing Traits for this type. For now I only implemented it for f32.

pub type Vec3<T = f32> = [T; 3];

pub trait Vector3<T> {
    /// Creates a new Vec3 with its values initialized to `[0.0, 0.0, 0.0]`.
    fn new() -> Self;

    /// Creates a new `Vec3` based on the given `x`, `y` and `z` values.
    fn from_values(x: f32, y: f32, z: f32) -> Vec3<T>;

    /// Calculates the scalar dot product of two `Vec3`'s.
    fn dot(a: Vec3<T>, b: Vec3<T>) -> f32;

    /// Performs multiplication between two `Vec3`.
    fn multiply(self, a: Vec3) -> Vec3;

    /// Calculates the sum of two `Vec3` components.Vec3
    fn add(self, a: Vec3) -> Vec3;

    /// Scales a `Vec3` by a scalar value.
    fn scale(self, x: f32) -> Vec3;
}

impl Vector3<f32> for Vec3<f32> {
    /// Creates a new Vec3 with its values initialized to `[0.0, 0.0, 0.0]`.
    ///
    /// ```
    /// use glMatrix_rs::vec3::*;
    ///
    /// let result = Vec3::new();
    /// assert_eq!([0.0, 0.0, 0.0], result);
    /// ```
    fn new() -> Vec3<f32> {
        [0.0, 0.0, 0.0]
    }

    /// Creates a new `Vec3` based on the given `x`, `y` and `z` values.
    ///
    /// ### Arguments
    ///
    /// * `x` - The first vector component.
    /// * `y` - The second vector component.
    /// * `z` - The third vector component.
    ///
    /// ```
    /// use glMatrix_rs::vec3::*;
    ///
    /// let result = Vec3::from_values(0.0, 1.0, 2.0);
    /// assert_eq!([0.0, 1.0, 2.0], result);
    /// ```
    fn from_values(x: f32, y: f32, z: f32) -> Vec3 {
        [x, y, z]
    }

    /// Calculates the scalar dot product of two `Vec3`'s.
    ///
    /// ### Arguments
    ///
    /// * `a` - The first vector for `dot` calculation.
    /// * `b` - The second  vector for `dot` calculation.
    ///
    /// ```
    /// use glMatrix_rs::vec3::*;
    /// let a = Vec3::from_values(2.0, 2.0, 2.0);
    /// let b = Vec3::from_values(2.0, 2.0, 2.0);
    /// assert_eq!(12.0, Vec3::dot(a, b));
    /// ```
    fn dot(a: Vec3, b: Vec3) -> f32 {
        a[0] * b[0] + a[1] * b[1] + a[2] * b[2]
    }

    /// Performs multiplication between two `Vec3`.
    ///
    /// ### Arguments
    /// 
    /// * `a` - Vector by which `self` will be multiplied.
    ///
    /// ```
    /// use glMatrix_rs::vec3::*;
    /// let mut out = Vec3::new();
    /// let a = Vec3::from_values(1.0, 2.0, 3.0);
    /// let b = Vec3::from_values(2.0, 2.0, 2.0);
    /// assert_eq!([2.0, 4.0, 6.0], a.multiply(b));
    /// ```
    fn multiply(self, a: Vec3) -> Vec3 {
        [self[0] * a[0], self[1] * a[1], self[2] * a[2]]
    }

    /// Calculates the sum of two `Vec3` components.Vec3
    ///
    /// ### Arguments
    /// 
    /// * `a` - Vector which will be added to `self`.
    ///
    /// ```
    /// use glMatrix_rs::vec3::*;
    /// let x = Vec3::from_values(1.0, 2.0, 3.0);
    /// let a = Vec3::from_values(3.0, 2.0, 1.0);
    /// assert_eq!([4.0, 4.0, 4.0], x.add(a));
    /// ```
    fn add(self, a: Vec3) -> Vec3 {
        [self[0] + a[0], self[1] + a[1], self[2] + a[2]]
    }

    /// Scales a `Vec3` by a scalar value.
    ///
    /// ### Arguments
    /// 
    /// * `x` - Scalar value by which the vector will be scaled. 
    ///
    /// ```
    /// use glMatrix_rs::vec3::*;
    /// let a = Vec3::from_values(1.0, 2.0, 3.0);
    /// assert_eq!([2.0, 4.0, 6.0], a.scale(2.0));
    /// ```
    fn scale(self, x: f32) -> Vec3 {
        [self[0] * x, self[1] * x, self[2] * x]
    }
}

You can clone, build and test it any time from my GitHub repository.

\$\endgroup\$
3
\$\begingroup\$

My first recommendation is to use std::ops. Try writing implementations of ops::Add for addition, ops::Mul<Vec3<T>> for cross product, and ops::Mul<T> for scaler-vector multiplication. Not only does implementing these standard traits make your code more interoperable with other Rust code, but it also gives you operator overloading, so you can use + and * on your vectors.

Second, I would question the user of the trait Vector3<f32>. Why do you need a trait? I would just put your operations in the implementation of Vec3<T> itself

impl <T> Vec3<T> {

// ... methods ...

}

Looking forward to seeing your completed code. This will even more interesting once you get generics working.

\$\endgroup\$
  • \$\begingroup\$ Thanks for your answer. I think I need the traits, as my type alias does not create a new type. If I try your approach I get impl requires a base type. Also see this Github issue. \$\endgroup\$ – チーズパン May 17 at 16:14
  • \$\begingroup\$ Oh, I missed that. If you can't have a really struct, that is very limiting. Do you have objections to using a struct with an array member instead of a type alias? \$\endgroup\$ – Benjamin Kuykendall May 17 at 16:26
  • \$\begingroup\$ It depends on which advantages and disadvantages the two approaches have :) Not sure about this. \$\endgroup\$ – チーズパン May 17 at 16:37

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.