Picking up where we left off in part 1, chapter 2 of Peter Shirley's book Ray Tracing in One Weekend introduces us to the class vec3. Vec3 allows for creating column vectors with three elements. The class has various methods that allows for finding the dot product, length of the vector, cross product etc. and also several operational overloads for vector addition, subtraction, division etc. Let's rewrite vec3 to rust!

First stumbling block is that rust technically doesn't support classes, it does however have structs, and we can write methods for those structs. To start with we define the struct Vec3 which contains an array with three elements, capital V because rust wants struct names to use camel case.

struct Vec3 {
  e: [f64; 3],
}

To write methods for a struct like we would write methods for Java or C++ we use the impl keyword. For example implementing a method r() that will return the red level of the vector:

 
fn r(&self) -> f64 { 
  self.e[0]
} 

Operator overloading

Rust also supports operator overloading using the impl keyword and a trait. example the + operator can be overloaded using the Add trait or the [ ] operator can be overloaded using the Index trait, remember to import the trait! use std::ops::Index;:

 
impl Index<usize> for Vec3 {
    type Output = f64;

    fn index<'a>(&'a self, index: usize) -> &'a f64 {
        &self.e[index]
    }
}

Then instead of

 
  println!("P3\n{} {} {}\n",v.e[0],v.e[1],v.e[2]);

We can do

 
  println!("P3\n{} {} {}\n",v[0],v[1],v[2]);

Constructor

Rust also supports constructors, well technically it is not a constructor, it's just a function commonly named new that makes creation of struct types easier, but new is not a special keyword in rust and does not necessarily put data on the heap. If you want to put data on the heap, use the Box syntax. We can implement new like this:

 
impl Vec3 {
  pub fn new(e0: f64, e1: f64, e2: f64) -> Vec3 {
    Vec3 { e: [e0, e1, e2]}
  }

And calling new like below will put data on the stack, not on the heap.

 
  let v = vec3::Vec3::new(3.14,2.71,4.2);

Observe the difference to the 'normal' way of instanciation struct types

 
  let v2 = vec3::Vec3 { e: [3.14,2.71,4.2] };

Modules

There are no .h files in rust like there are in C. However you can write seperate .rs files and import them as modules in a different .rs file. The Vec3 struct has been put in a seperate file named vec3.rs, which is why when instanciating a Vec3 data type in the Constructor section above we need to do vec3::Vec3 instead of simply Vec3. The file also needs to be importet into the main file using the mod vec3;. Keep in mind that structs you want to use and functions that you want to call from outside the scope of the vec3 file, e.g. from the main file, needs to be declared as pub, like this:

 
pub struct Vec3 {
  pub e: [f64; 3],
}

impl Vec3 {
  pub fn new(e0: f64, e1: f64, e2: f64) -> Vec3 {
    Vec3 { e: [e0, e1, e2]}
  }
}

If you don't you will be faced with compiler errors:

 
vec_image.rs:6:11: 6:26 error: method `new` is private
vec_image.rs:6   let v = vec3::Vec3::new(3.14,2.71,4.2);
                         ^~~~~~~~~~~~~~~
error: aborting due to previous error

That's it for chapter 2! If you want to see the source code discussed in this post, go to github.