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
inC
- 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
i32
on thestack
- Copies the bit pattern representing the value
10
to the allocated memory - 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;
let v = ...
allocates memory for the vector object on thestack
- Actual data
[1, 2, 3]
is put on theheap
- Vector object
v
then is given the address to the data on the heap - Currently: vector on the
stack
with address pointing to data on theheap
- Move
v
tov2
bitwise copy of the vectorv
into 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 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 { //... }
.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:
copy
the element and return it- move
ownership
of element to caller
Since we're working with a
generic
typeT
, there was no guarantee thatT
could be copied> Rust tries to move the =ownership
, which we cannot do for indexed content AAAAANDpanic!
. This is solved by making sure thatT
does in fact implement theCopy
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 theCopy
route as it is cleaner (mesa thinks).Not making sure that the
generic
typeT
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>
traitimpl<T: Copy + Add<T, Output = T>> Add for Matrix<Vec<T>> { // ... }
- Attempting to
println!("{:?}", matrix)
without implementingDebug
trait. 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
a
andb
: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 theshape
attribute being of typeVec<usize>
which does not implement theCopy
trait
. Thistrait
is not something you can override, as it is being performed simply by bitwise copying. Thus we have to explicitly implement theClone
trait
and 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]] }