Rust

Table of Contents

General

Ownership system

Ownership

The reason for using Vec a lot in this section is because this does not implement the Copy trait, and so will not implicitly be copied. If we were to use say an integer, i32, then doing let y = x would actually do let y = x.clone(), which would NOT transfer ownership, but instead create a copy of x and so we could still make use of the variable x.

  • Only one binding to any resource

    let x = vec![1];
    let y = x;  // <= ownership of thing at ´x´ passed to ´y´
    x
    
  • Passing a variable to a function => function takes ownership.

    let x = vec![1];
    
    fn return_this<T>(this: T) -> T {
        this
    }
    
    return_this(x);  // <= ownership is moved into `return_this`
    x
    
error[E0382]: use of moved value: `x`
 --> /var/folders/m4/qljwzcp91hbgq46hrlp9_4bc0000gp/T/babel-12113tqt/rust-12113Lab:9:18
  |
8 | return_this(x);
  |             - value moved here
9 | println!("{:?}", x);
  |                  ^ value used here after move
  |
  = note: move occurs because `x` has type `std::vec::Vec<i32>`, which does not implement the `Copy` trait

error: aborting due to previous error
  • Function can still pass ownership back to caller
let x = vec![1];

fn return_this<T>(this: T) -> T {
    this
}

let y = return_this(x);
y
[1]

  • When a binding goes out of scope => resource is deallocated
    • Makes it so that we don't have to worry about explicitly freeing memory using methods like free in C
A bit in-depth

When we write

let x = 10;

Rust does the following:

  1. Allocates memory for an integer i32 on the stack
  2. Copies the bit pattern representing the value 10 to the allocated memory
  3. Binds the variable x to this memory region

Now consider:

let v = vec![1, 2, 3];  // <= allocates memory for vector object `v` on the stack
let mut v2 = v;
  1. let v = ... allocates memory for the vector object on the stack
  2. Actual data [1, 2, 3] is put on the heap
  3. Vector object v then is given the address to the data on the heap
  4. Currently: vector on the stack with address pointing to data on the heap
  5. Move v to v2 rust_1637525cf02f6fd6d9c4b23a99fde4e4c0783292.png bitwise copy of the vector

v into the stack allocation represented by v2.

At this point both v and v2 are pointing to the same data on the heap, and so if v2 is allowed the mut reference it wants, it could change the data without v knowing! Crazy, am I right?!

The way Rust actually does memory allocation on the heap is using the Box<T> object. For example:

let x = Box::new(3);
println!("{:p}", x);  // <= prints the memory address
println!("{}", x);    // <= the format function simply derefs for us
println!("{}", *x);   // <= explicitly dereffing

0x106e15008
3
3

Will allocate memory for the integer 3 on the heap, and then bind the variable x on the stack to it's address.

Box<T> implements a trait called Drop, which deallocates memory for a resource whenever the Box<T> (binding) goes out of scope.

References and borrowing

To allow for easier use, Rust provides us with borrowing using references.

fn return_this(x: &Vec<i32>) -> &Vec<i32> {
    let y = x;  // <= would NOT work if `x` was dereferenced here
    x
}

let a = vec![1];
return_this(&a)
[1]

Rules for borrowing

The following needs to be satisfied for any resource at any time:

  • One or more references (&T) to a resource
  • Exactly one mutable reference (&mut T)

Lifetime

It's good to remember that lifetime annotations are descriptive and not prescriptive. You can't change lifetime of a variable by adding lifetime annotations. For example, local variables of a function get destroyed on function exit no matter what and you can't prevent it by adding lifetime annotation to a local variable reference you are trying to return.

Provides functionality to specify when bindings should go out of scope. The use case is as follows:

  1. I acquire a handle to some kind of resource.
  2. I lend you a reference to the resource.
  3. I decide I’m done with the resource, and deallocate it, while you still have your reference.
  4. You decide to use the resource after I deallocated

This is not something Rust allows, and to allow the programmer to specify how long these referenced memory allocations should live, we use the following syntax:

struct Foo<'a> {  // <= declares the lifetime `a` for `Foo`
    x: &'a i32    // <= says that the value reffed by `x` will follow the lifetime `a`
}

Here we make sure that the value referenced by x will live as long as Foo is alive. And so we don't have to worry about attempting to access deallocated memory segments.

A bit more complex problem using generics:

use std::fmt::Debug;

#[derive(Debug)]
struct Foo<'a, 'b, T: 'a, U: 'a + 'b> {
    data1: &'a T,
    data2: &'a U,
    data3: &'b U
}

let a = 1;
let b = 2;

let x = Foo {
    data1: &[1, 2, 3],
    data2: &a,
    data3: &b
};

x
Foo { data1: [1, 2, 3], data2: 1, data3: 2 }

Also works for functions:

fn ref_test<'a>(x: &'a i32) -> &'a i32 {
    x
}

let x = 1;
ref_test(&x)
1

Simple projects

Matrices

Setup

[project]
name = "matrix"
version = "0.1.0"
authors = ["Tor Erlend Fjelde"]

[dependencies]
num = "0.1.37"
num-traits = "0.1.37"
ndarray = "0.7.3"

Struct

type Shape = Vec<usize>;

struct Matrix<T> {
    shape: Shape,
    data: Vec<Vec<T>>,
}

impl<T: Copy> Clone for Matrix<T> {
    fn clone(&self) -> Matrix<T> {
        Matrix {
            shape: self.shape.to_vec(),
            data: self.data.to_vec()
        }
    }
}

Macros

macro_rules! vector {
    ( $( $x:expr ),* ) => {
        {
            let mut temp_vec = Vec::new();

            $(
                let mut temp_row = Vec::new();
                temp_row.push($x);
                temp_vec.push(temp_row);
            )*;

            let mut _shape = Vec::new();
            _shape.push(temp_vec.len());
            _shape.push(1);

            Matrix {
                shape: _shape,
                data: temp_vec
            }
        }
    }
}
macro_rules! matrix {
    ( $( [ $( $x:expr ),* ] ),* ) => {
        {
            let mut _cols: usize = 0;
            let mut _rows: usize = 0;
            let mut temp_vec = Vec::new();
            $(
                let mut temp_row = Vec::new();
                $(
                    temp_row.push($x);
                )*
                _cols = temp_row.len();
                temp_vec.push(temp_row);
            )*
                _rows = temp_vec.len();

            let mut _shape: Vec<usize> = Vec::new();
            _shape.push(_rows);
            _shape.push(_cols);

            Matrix {
                shape: _shape,
                data: temp_vec
            }
        }
    };
}

Implementing traits

Addition
extern crate num_traits;

use num_traits::identities::Zero;
use num_traits::identities::zero;

impl<T: Zero + Copy + Add<T, Output = T>> Add for Matrix<T> {
    type Output = Matrix<T>;

    fn add(self, other: Matrix<T>) -> Matrix<T> {
        assert_eq!(self.shape[0], other.shape[0]);
        assert_eq!(self.shape[1], other.shape[1]);

        let shape: Vec<usize> = vec![self.shape[0], self.shape[1]];
        let mut data: Vec<Vec<T>> = vec![vec![zero(); shape[1]]; shape[0]];

        for i in 0..shape[0] {
            for j in 0..shape[1] {
                data[i][j] = self.data[i][j] + other.data[i][j];
            }
        }

        Matrix {
            shape: shape,
            data: data,
        }
    }
}
Dot product
pub trait Dot<RHS = Self> {
    type Output;

    fn dot(self, rhs: RHS) -> Self::Output;
}

impl<T: fmt::Debug + Zero + Copy + Mul<T, Output=T> + Add<T, Output=T> + AddAssign<T>> Dot for Matrix<T> {
    type Output = T;

    fn dot(self, rhs: Matrix<T>) -> T {
        // Making sure they are vectors
        assert_eq!(self.shape[0], 1);
        assert_eq!(rhs.shape[1], 1);
        // And that the dimensions match
        assert_eq!(self.shape[0], rhs.shape[1]);

        let mut res: T = zero();

        for i in 0..self.shape[1] {
            res += self.data[0][i] * rhs.data[i][0];
        }

        res
    }
}
Multiplication

Multiplication is simply the dot-products between row vector of the left matrix with the column vector of the right matrix.

This part can be sooo much more efficient. I was having some issues with the fact that Vec<T> does not implement the Copy trait, and Copy is a trait which cannot be overriden. Thus I had to implement a Clone trait for Matrix<T>.

It would probably be more efficient to implement some method which constructs a 1D Matrix from a Vec<T>. Possibly using a clone_from() ?

impl<T: fmt::Display + fmt::Debug + Copy + Zero + Add<T, Output = T> + AddAssign<T> + Mul<T, Output=T>> Mul for Matrix<T> {
    type Output = Matrix<T>;

    fn mul(self, rhs: Matrix<T>) -> Matrix<T> {
        assert_eq!(self.shape[1], rhs.shape[0]);

        let shape: Vec<usize> = vec![self.shape[0], rhs.shape[1]];
        let mut data: Vec<Vec<T>> = Vec::new();

        for i in 0..self.shape[0] {
            let mut row: Vec<T> = Vec::new();

            /* Need to wrap the data in a vector so we can create a Matrix from it  */
            let mut lhs_row_data = Vec::new();
            // Grabbing the ith ROW vector
            lhs_row_data.push(self.data[i].to_vec());

            let lhs_row = Matrix {
                shape: vec![1, self.shape[1]],
                data: lhs_row_data
            };

            for j in 0..rhs.shape[1] {
                /* Same stuff here as for the lhs_row */
                let mut rhs_col_data = Vec::new();
                // Want the jth COLUMN => gotta iterate through each row, and
                // grab the jth entry
            for idx in 0..rhs.shape[0] {
                    rhs_col_data.push(vec![rhs.data[idx][j]]);
                }

                let rhs_col = Matrix {
                    shape: vec![rhs.shape[0], 1],
                    data: rhs_col_data
                };

                /*
                 * Here it's useful to take advantage of `to_owner()`
                 * since we won't be using `lhs_row` more, and so can
                 * simply pass the ownership instead of creating a copy.
                 */
                let dotted = lhs_row.to_owned().dot(rhs_col);

                row.push(dotted);
            }

            data.push(row);
        }

        Matrix {
            shape: vec![self.shape[0], rhs.shape[1]],
            data: data
        }
    }
}
Transpose
trait Transpose {
    type Output;

    fn T(&self) -> Self::Output;
}

impl<A: Zero + Copy> Transpose for Matrix<A> {
    type Output = Matrix<A>;

    fn T(&self) -> Matrix<A> {
        let mut shape = vec![self.shape[1], self.shape[0]];
        let mut data = vec![vec![zero(); self.shape[0]]; self.shape[1]];

        for i in 0..self.shape[0] {
            for j in 0..self.shape[1] {
                data[j][i] = self.data[i][j];
            }
        }

        Matrix {
            shape: shape,
            data: data
        }
    }
}
Elementwise application of functions
use num_traits::float::Float;

trait ElementwiseApply {
    type Output;

    fn apply(&self, f: &Fn(Self::Output) -> Self::Output) -> Matrix<Self::Output>;
}

impl<T: Copy + Float> ElementwiseApply for Matrix<T> {
    type Output = T;

    fn apply(&self, f: &Fn(T) -> T) -> Matrix<T> {
        let mut res = Matrix {
            shape: self.shape.to_vec(),
            data: self.data.to_vec()
        };

        for i in 0..self.shape[0] {
            for j in 0..self.shape[1] {
                res.data[i][j] = f(self.data[i][j])
            }
        }

        res
    }
}
Mathematical operations
impl<T: Float> Matrix<T> {
    fn exp(x: &Matrix<T>) -> Matrix<T> {
        x.apply(&Float::exp)
    }

    fn ln(x: &Matrix<T>) -> Matrix<T> {
        x.apply(&Float::ln)
    }

    fn powi(x: &Matrix<T>, power: i32) -> Matrix<T> {
        x.apply(&|el| el.powi(power))
    }

fn powf(x: &Matrix<T>, power: T) -> Matrix<T> {
        x.apply(&|el| el.powf(power))
    }

    fn log(x: &Matrix<T>, base: T) -> Matrix<T> {
        x.apply(&|el| el.log(base))
    }
}
Debug
use std::fmt;


impl<T> fmt::Debug for Matrix<T> where T: fmt::Display{
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "Matrix {{ rows: {}, cols: {} }}", self.shape[0], self.shape[1])
            .unwrap();
        for i in 0..self.shape[0] {
            write!(f, "\n[").unwrap();
            for j in 0..self.shape[1] {
                write!(f, " {:5} ", self.data[i][j]).unwrap();
            }
            write!(f, "]").unwrap();
        }
        write!(f, "")
    }
}

Conclusion

use std::ops::Add;
use std::ops::Mul;
use std::ops::AddAssign;

type Shape = Vec<usize>;

struct Matrix<T> {
    shape: Shape,
    data: Vec<Vec<T>>,
}

impl<T: Copy> Clone for Matrix<T> {
    fn clone(&self) -> Matrix<T> {
        Matrix {
            shape: self.shape.to_vec(),
            data: self.data.to_vec()
        }
    }
}

/* Formatting */
use std::fmt;


impl<T> fmt::Debug for Matrix<T> where T: fmt::Display{
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "Matrix {{ rows: {}, cols: {} }}", self.shape[0], self.shape[1])
            .unwrap();
        for i in 0..self.shape[0] {
            write!(f, "\n[").unwrap();
            for j in 0..self.shape[1] {
                write!(f, " {:5} ", self.data[i][j]).unwrap();
            }
            write!(f, "]").unwrap();
        }
        write!(f, "")
    }
}


/* Macros */
macro_rules! vector {
    ( $( $x:expr ),* ) => {
        {
            let mut temp_vec = Vec::new();

            $(
                let mut temp_row = Vec::new();
                temp_row.push($x);
                temp_vec.push(temp_row);
            )*;

            let mut _shape = Vec::new();
            _shape.push(temp_vec.len());
            _shape.push(1);

            Matrix {
                shape: _shape,
                data: temp_vec
            }
        }
    }
}
macro_rules! matrix {
    ( $( [ $( $x:expr ),* ] ),* ) => {
        {
            let mut _cols: usize = 0;
            let mut _rows: usize = 0;
            let mut temp_vec = Vec::new();
            $(
                let mut temp_row = Vec::new();
                $(
                    temp_row.push($x);
                )*
                _cols = temp_row.len();
                temp_vec.push(temp_row);
            )*
                _rows = temp_vec.len();

            let mut _shape: Vec<usize> = Vec::new();
            _shape.push(_rows);
            _shape.push(_cols);

            Matrix {
                shape: _shape,
                data: temp_vec
            }
        }
    };
}

/* Operations */
extern crate num_traits;

use num_traits::identities::Zero;
use num_traits::identities::zero;

impl<T: Zero + Copy + Add<T, Output = T>> Add for Matrix<T> {
    type Output = Matrix<T>;

    fn add(self, other: Matrix<T>) -> Matrix<T> {
        assert_eq!(self.shape[0], other.shape[0]);
        assert_eq!(self.shape[1], other.shape[1]);

        let shape: Vec<usize> = vec![self.shape[0], self.shape[1]];
        let mut data: Vec<Vec<T>> = vec![vec![zero(); shape[1]]; shape[0]];

        for i in 0..shape[0] {
            for j in 0..shape[1] {
                data[i][j] = self.data[i][j] + other.data[i][j];
            }
        }

        Matrix {
            shape: shape,
            data: data,
        }
    }
}
use num_traits::float::Float;

trait ElementwiseApply {
    type Output;

    fn apply(&self, f: &Fn(Self::Output) -> Self::Output) -> Matrix<Self::Output>;
}

impl<T: Copy + Float> ElementwiseApply for Matrix<T> {
    type Output = T;

    fn apply(&self, f: &Fn(T) -> T) -> Matrix<T> {
        let mut res = Matrix {
            shape: self.shape.to_vec(),
            data: self.data.to_vec()
        };

        for i in 0..self.shape[0] {
            for j in 0..self.shape[1] {
                res.data[i][j] = f(self.data[i][j])
            }
        }

        res
    }
}
pub trait Dot<RHS = Self> {
    type Output;

    fn dot(self, rhs: RHS) -> Self::Output;
}

impl<T: fmt::Debug + Zero + Copy + Mul<T, Output=T> + Add<T, Output=T> + AddAssign<T>> Dot for Matrix<T> {
    type Output = T;

    fn dot(self, rhs: Matrix<T>) -> T {
        // Making sure they are vectors
        assert_eq!(self.shape[0], 1);
        assert_eq!(rhs.shape[1], 1);
        // And that the dimensions match
        assert_eq!(self.shape[0], rhs.shape[1]);

        let mut res: T = zero();

        for i in 0..self.shape[1] {
            res += self.data[0][i] * rhs.data[i][0];
        }

        res
    }
}
trait Transpose {
    type Output;

    fn T(&self) -> Self::Output;
}

impl<A: Zero + Copy> Transpose for Matrix<A> {
    type Output = Matrix<A>;

    fn T(&self) -> Matrix<A> {
        let mut shape = vec![self.shape[1], self.shape[0]];
        let mut data = vec![vec![zero(); self.shape[0]]; self.shape[1]];

        for i in 0..self.shape[0] {
            for j in 0..self.shape[1] {
                data[j][i] = self.data[i][j];
            }
        }

        Matrix {
            shape: shape,
            data: data
        }
    }
}
impl<T: fmt::Display + fmt::Debug + Copy + Zero + Add<T, Output = T> + AddAssign<T> + Mul<T, Output=T>> Mul for Matrix<T> {
    type Output = Matrix<T>;

    fn mul(self, rhs: Matrix<T>) -> Matrix<T> {
        assert_eq!(self.shape[1], rhs.shape[0]);

        let shape: Vec<usize> = vec![self.shape[0], rhs.shape[1]];
        let mut data: Vec<Vec<T>> = Vec::new();

        for i in 0..self.shape[0] {
            let mut row: Vec<T> = Vec::new();

            /* Need to wrap the data in a vector so we can create a Matrix from it  */
            let mut lhs_row_data = Vec::new();
            // Grabbing the ith ROW vector
            lhs_row_data.push(self.data[i].to_vec());

            let lhs_row = Matrix {
                shape: vec![1, self.shape[1]],
                data: lhs_row_data
            };

            for j in 0..rhs.shape[1] {
                /* Same stuff here as for the lhs_row */
                let mut rhs_col_data = Vec::new();
                // Want the jth COLUMN => gotta iterate through each row, and
                // grab the jth entry
            for idx in 0..rhs.shape[0] {
                    rhs_col_data.push(vec![rhs.data[idx][j]]);
                }

                let rhs_col = Matrix {
                    shape: vec![rhs.shape[0], 1],
                    data: rhs_col_data
                };

                /*
                 * Here it's useful to take advantage of `to_owner()`
                 * since we won't be using `lhs_row` more, and so can
                 * simply pass the ownership instead of creating a copy.
                 */
                let dotted = lhs_row.to_owned().dot(rhs_col);

                row.push(dotted);
            }

            data.push(row);
        }

        Matrix {
            shape: vec![self.shape[0], rhs.shape[1]],
            data: data
        }
    }
}
impl<T: Float> Matrix<T> {
    fn exp(x: &Matrix<T>) -> Matrix<T> {
        x.apply(&Float::exp)
    }

    fn ln(x: &Matrix<T>) -> Matrix<T> {
        x.apply(&Float::ln)
    }

    fn powi(x: &Matrix<T>, power: i32) -> Matrix<T> {
        x.apply(&|el| el.powi(power))
    }

fn powf(x: &Matrix<T>, power: T) -> Matrix<T> {
        x.apply(&|el| el.powf(power))
    }

    fn log(x: &Matrix<T>, base: T) -> Matrix<T> {
        x.apply(&|el| el.log(base))
    }
}

let a: Matrix<i32> = Matrix {
    shape: vec![2, 2],
    data: vec![
        vec![1, 1],
        vec![2, 2]
    ]
};
let b: Matrix<i32> = matrix![
    [3, 4],
    [5, 6]
];
let c = matrix![
    [1., 2.],
    [3., 4.]
];

println!("Addition: {:?}\n", a.to_owned() + b.to_owned());
println!("Multiplication: {:?}\n", a * b);
println!("Transposed: {:?}\n", c.T());

println!("Exponential: {:?}\n", Matrix::exp(&c));
println!("Natural logarithm: {:?}\n", Matrix::ln(&Matrix::exp(&c)));
println!("To the power of 2: {:?}\n", Matrix::powi(&c, 2));
println!("Logarithm base 2: {:?}\n", Matrix::log(&c, 2.));
Addition: Matrix { rows: 2, cols: 2 }
[     4      5 ]
[     7      8 ]

Multiplication: Matrix { rows: 2, cols: 2 }
[     8     10 ]
[    16     20 ]

Transposed: Matrix { rows: 2, cols: 2 }
[     1      3 ]
[     2      4 ]

Exponential: Matrix { rows: 2, cols: 2 }
[ 2.718281828459045  7.38905609893065 ]
[ 20.085536923187668  54.598150033144236 ]

Natural logarithm: Matrix { rows: 2, cols: 2 }
[     1      2 ]
[     3      4 ]

To the power of 2: Matrix { rows: 2, cols: 2 }
[     1      4 ]
[     9     16 ]

Logarithm base 2: Matrix { rows: 2, cols: 2 }
[     0      1 ]
[ 1.5849625007211563      2 ]

Mistakes & Issues

  1. error: chained comparison operators require parantheses due to specifying the generic type on at the creation of the return value:

    impl<T> Add for Matrix<Vec<T>> {
        type Output = Matrix<Vec<T>>;
    
        fn add(self, other: Matrix<Vec<T>>) -> Matrix<Vec<T>> {
            // ...
    
            // MISTAKE #1
            Matrix<Vec<T>> {
                // ...
            }
        }
    }
    

    This is due to the fact that Rust actually infers the type from the function signature, and so this is wrong, and should in fact just be Matrix { //... } .

  2. Trying to access nested vector using indexing inside the addition loop.

    error[E0507]: cannot move out of indexed content
      --> /var/folders/m4/qljwzcp91hbgq46hrlp9_4bc0000gp/T/babel-97434Ysa/rust-97434gbC:29:32
       |
    29 |                 let v_val: T = self.data[i][j] + other.data[j][i];
       |                                ^^^^^^^^^^^^^^^ cannot move out of indexed content
    

    When trying to access an element by index, Rust will attempt the following:

    1. copy the element and return it
    2. move ownership of element to caller

    Since we're working with a generic type T, there was no guarantee that T could be copied > Rust tries to move the =ownership, which we cannot do for indexed content AAAAAND panic!. This is solved by making sure that T does in fact implement the Copy trait.

    impl<T: Copy> Add for Matrix<Vec<T>> {
        // ...
    }
    

    There is also the option of taking grabbing the reference to the entries using &v[i] buuut we chose the Copy route as it is cleaner (mesa thinks).

  3. Not making sure that the generic type T implements +

    error[E0369]: binary operation `+` cannot be applied to type `T`
      --> /var/folders/m4/qljwzcp91hbgq46hrlp9_4bc0000gp/T/babel-97434Ysa/rust-97434vgi:29:32
       |
    29 |                 let v_val: T = self.data[i][j] + other.data[j][i];
       |                                ^^^^^^^^^^^^^^^
       |
    note: an implementation of `std::ops::Add` might be missing for `T`
    

    Which is fixed by the mere addition of the Add<T, Output = T> trait

    impl<T: Copy + Add<T, Output = T>> Add for Matrix<Vec<T>> {
        // ... 
    }
    
  4. Attempting to println!("{:?}", matrix) without implementing Debug trait. This solution you can simply see in the actual implementation of the Debug trait.
  5. Have to make use of explicit cloning or pass-by-ref when doing simply operations on a Matrix<T>.

    This is the wanted functionality:

    let a = 1;
    let b = 2;
    
    println!("{:?}", a + b);
    println!("{:?}", a - b);
    

    This is what we get if we try to do that using matrices for a and b:

    error[E0382]: use of moved value: `a`
       --> /var/folders/m4/qljwzcp91hbgq46hrlp9_4bc0000gp/T/babel-12113tqt/rust-12113Z9k:268:36
        |
    267 | println!("Addition: {:?}\n", a + b);
        |                              - value moved here
    268 | println!("Multiplication: {:?}\n", a * b);
        |                                    ^ value used here after move
        |
        = note: move occurs because `a` has type `main::Matrix<i32>`, which does not implement the `Copy` trait
    
    error[E0382]: use of moved value: `b`
       --> /var/folders/m4/qljwzcp91hbgq46hrlp9_4bc0000gp/T/babel-12113tqt/rust-12113Z9k:268:40
        |
    267 | println!("Addition: {:?}\n", a + b);
        |                                  - value moved here
    268 | println!("Multiplication: {:?}\n", a * b);
        |                                        ^ value used here after move
        |
        = note: move occurs because `b` has type `main::Matrix<i32>`, which does not implement the `Copy` trait
    
    error: aborting due to 2 previous errors
    

    Not cool! This is due to the fact that we do not have implicit cloning for our Matrix<T> struct, due to the shape attribute being of type Vec<usize> which does not implement the Copy trait. This trait is not something you can override, as it is being performed simply by bitwise copying. Thus we have to explicitly implement the Clone trait and make the transfer of ownership explicit by using methods as a.to_owned() or pass by reference.

Fun stuff

Matrices v2

Goal

If we could implement Matrix struct by using Arrays , i.e. &[1, 2, 3, ... ] instead, we could get the wanted functionality of a + b with implicit Copy instead of having to explicitly pass ownership.

Struct

use std::fmt::Debug;

#[derive(Debug)]
struct Matrix<'a, T: 'a> {
    shape: &'a [usize],
    data: &'a [ &'a[T] ],
}

impl<'a, T: Copy> Copy for Matrix<'a, T> { }


impl<'a, T: Copy> Clone for Matrix<'a, T> {
    fn clone(&self) -> Matrix<'a, T> {
        Matrix {
            shape: self.shape,
            data: self.data
        }
    }
}

let x = Matrix::<i32> {
    shape: &[2, 3],
    data: &[ 
        &[1, 2, 3],
        &[4, 5, 6]
    ]
};
x
Matrix { shape: [2, 3], data: [[1, 2, 3], [4, 5, 6]] }