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 errorFunction 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
freeinC
- Makes it so that we don't have to worry about explicitly freeing memory using methods like
A bit in-depth
When we write
let x = 10;
Rust does the following:
- Allocates memory for an integer
i32on thestack - Copies the bit pattern representing the value
10to the allocated memory - Binds the variable
xto this memory region
Now consider:
let v = vec![1, 2, 3]; // <= allocates memory for vector object `v` on the stack let mut v2 = v;
let v = ...allocates memory for the vector object on thestack- Actual data
[1, 2, 3]is put on theheap - Vector object
vthen is given the address to the data on the heap - Currently: vector on the
stackwith address pointing to data on theheap - Move
vtov2
bitwise copy of the vector
vinto the stack allocation represented byv2.
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:
- I acquire a handle to some kind of resource.
- I lend you a reference to the resource.
- I decide I’m done with the resource, and deallocate it, while you still have your reference.
- 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
error: chained comparison operators require paranthesesdue 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 { //... }.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:
copythe element and return it- move
ownershipof element to caller
Since we're working with a
generictypeT, there was no guarantee thatTcould be copied> Rust tries to move the =ownership, which we cannot do for indexed content AAAAANDpanic!. This is solved by making sure thatTdoes in fact implement theCopytrait.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 theCopyroute as it is cleaner (mesa thinks).Not making sure that the
generictypeTimplements+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>traitimpl<T: Copy + Add<T, Output = T>> Add for Matrix<Vec<T>> { // ... }
- Attempting to
println!("{:?}", matrix)without implementingDebugtrait. This solution you can simply see in the actual implementation of the Debug trait. 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
aandb: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 errorsNot cool! This is due to the fact that we do not have implicit cloning for our
Matrix<T>struct, due to theshapeattribute being of typeVec<usize>which does not implement theCopytrait. Thistraitis not something you can override, as it is being performed simply by bitwise copying. Thus we have to explicitly implement theClonetraitand make the transfer of ownership explicit by using methods asa.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]] }