Saturday, February 29, 2020

Rust Ownership Rules

If you have been following this blog, then it would have been obvious that at the beginning of this year, I started learning Rust. This blogpost is a breakaway from the journal style of capturing the main points I encountered while reading the Rust book. It instead captures my understanding thus far of Rust ownership rules.

One of Rust's main differentiator is that it provides memory safety. This it does by providing compile-time guarantees that flag code that potentially could lead to memory bugs as a compile-time error. The compile-time guarantees enforce what is normally referred to as ownership rules. In this post, I took the opportunity to re-summarise what I consider to be the essence of this ownership rules in Rust. The key points can be outlined as follows:
  • Values are owned by variables. 
  • When the owning variables go out of scope, the memory the value is occupying will be deallocated.
  • The values can be used by other variables, they just need to adhere to certain rules that are enforced by the compiler.
The ways that other variables make use of value can be grouped into 4 categories and these ways of usage will dictate the rules to be adhered to:
  • Clone: Here the value is copied to the other variable. The other variable gets its own ownership of the copied value, while the original variable keeps the ownership of its value.
  • Move. Here the ownership is handed over to the other variable that wants to make use of the value. The original variable no longer has ownership.
  • Immutable Borrow. Here no ownership transfer occurs but the value can be accessed for reading by another variable. The memory is not de-allocated if the borrowing variable goes out of scope, since the borrowing variable does not have ownership.
  • Mutable Borrow. Here the value can be accessed for both reading and writing by the other variable. The memory is also not de-allocated if this borrowing variable goes out of scope since the borrowing variable does not have ownership.

Tuesday, February 04, 2020

Learning Rust - Day 9 - Closures and Iterators

This is the 9th entry of my learning Rust journal...

It captures some of the learning points while going through chapter 13 of the Rust Book. You can read other posts in this series by following the label learning rust.

This chapter did not present any new or mind bending concepts. Most modern languages nowadays have the concept of functions as first class citizens, closures and iterators. So the chapter was about taking note of how these concepts are encoded in Rust.

I did not really enjoy how this particular chapter was written, especially section 13.01. I think the pedagogy can be improved. The section spends way too much time in motivating an example, that in my opinion, clouds the essence of what is being explained. It so happened that I found another book on Rust Introduction to Rust which ended up being really well written: concise and well explained. I personally enjoyed the chapter on closure from this book than I did reading the Rust Book itself.

So to the content of this chapter, here are some of the things that stood out:


Functions as Values

// normal function definition
fn add(a:u32, b:u32) -> u32 {
    a + b

// storing defined function into a variable
// keyword fn is used to define the type of a function
let adder_func : fn(u32, u32) -> (u32) = add;

assert_eq!(adder_func(2,3), 5)

Defining Closures

let max = |x: i32, y:i32| {
    if x > y {
    } else {

assert_eq!(max(100,10), 100)

Closures do not need to have the type annotations specified, and if the body is a single expression, can be written in one line without the curly braces. Hence the above can also be written as:

let max = |x, y| if x > y { x } else { y };

assert_eq!(max(100,10), 100)


Closures can capture values from their environment in three ways, which directly map to the three ways ownership and references work in Rust:
  1. Taking ownership
  2. Immutable borrow (shared reference)
  3. Mutable borrow
These three ways corresponds to 3 traits.
  1. FnOnce
  2. Fn
  3. FnMut
Talking about traits, it looks like the implementation of closures in Rust can be approximated to having a backing struct with these traits implemented for it. I say approximated as I believe this is not 100% exactly how the compiler works. Using the approximation, if we have the following definition:

let mut a = vec![1, 2];
let mut b = vec![3, 4];
let mut c = vec![5, 6];
let my_closure = || {
    // Takes `a` by shared reference    
    assert_eq!(a[0], 1);
    // Takes `b` by mutable reference    
b[0] = 1;
    // Moves `c` into the closure    
    let d = c;


The above would be transformed into something like:

// Struct to represent the type of the closure
struct StructForMyClosure {
    a: &Vec<i32>,
    b: &mut Vec<i32>,
    c: Vec<i32>,

// Capture the variables in the environment
let x = StructForMyClosure{ a: &a, b: &mut b, c: c };

// Implements a trait..
impl FnOnce for StructForMyClosure {
    fn call_once(self) {
        assert_eq!(self.a[0], 1);
        self.b[0] = 1;
        let d = self.c;

// my_closure() would internally call the trait function

.iter() vs .iter_mut() vs .into_iter()

Turning into iterators, one of the few things that stood out to me was the difference between .iter(), .iter_mut() and .into_iter(). Again, unsurprisingly, the differences is related to how Rust handle borrowing/references.


This allows having an iterator that have a shared reference with the vector. As seen in the snippet below:

let mut my_vector = vec![1, 2, 3, 4];

let mut iter = my_vector.iter();
// note &1 in Some(&1) indicating shared reference
assert_eq!(, Some(&1));

// can still use my_vector// since it was borrowed immutably


This allows for having an iterator that has a mutable borrow of the vector. As seen in the snippet below:

let mut my_vector = vec![1, 2, 3, 4];

let mut iter = my_vector.iter_mut();
// note &mut 1 in Some(&mut 1) indicating mutable borrow
assert_eq!(, Some(&mut 1));
assert_eq!(, Some(&mut 2));

// can still use my_vector
// since the mutable borrow is out of scope

Due to the mutable borrow the following won't compile, because the vector is being accessed when the mutable borrow is still in scope, violating the restriction that mutable borrow should be exclusive:

let mut my_vector = vec![1, 2, 3, 4];

let mut iter = my_vector.iter_mut();
// note &mut 1 in Some(&mut 1) indicating mutable borrow
assert_eq!(, Some(&mut 1));

// leads to compilation error

assert_eq!(, Some(&mut 2));



This methods leads to an iterator that takes ownership of the vector. This means after the iterator is used, the original vector cannot be used anymore. Hence below snippet will compile:

let mut my_vector = vec![1, 2, 3, 4];

let mut iter = my_vector.into_iter();
assert_eq!(, Some(1));

But this wont:

let mut my_vector = vec![1, 2, 3, 4];

let mut iter = my_vector.into_iter();
assert_eq!(, Some(1));

Note that on a normal day, iterators would not be constructed and consumed by direct calling the next() method, instead they would be used with language construct like for in, eg:

A more real life of .iter() method would look like: 

let mut my_vector = vec![1, 2, 3, 4];

for x in my_vector.iter() {
    println!("{}", x)

// prints [1, 2, 3, 4]
println!("{:?}", my_vector)

While that of iter_mut() would look like:

let mut my_vector = vec![1, 2, 3, 4];

for x in my_vector.iter_mut() { 
   // since we have iter_mut
   // we can mutate the contents of the vector  
*x = *x + 1;
println!("{}", x)

// prints mutated value [2, 3, 4, 5]
println!("{:?}", my_vector)

and usage of into_iter() would look like:

let mut my_vector = vec![1, 2, 3, 4];

for x in my_vector.into_iter() {
    println!("{}", x)

// uncommenting below would lead to compilation error
// since ownership got moved due to into_iter()
// println!("{:?}", my_vector)

Also, even though this was not mentioned in the chapter, I was interested in knowing how to get the index in for for in. Found this can be achieved by calling the enumerate() method on the iterator.

For example:

let mut my_vector = vec![1, 2, 3, 4];

for (k, v) in my_vector.iter().enumerate() {
    /** Below prints:    
     Index: 0, value: 1    
     Index: 1, value: 2    
     Index: 2, value: 3    
     Index: 3, value: 4    
   println!("Index: {}, value: {}", k,v)

Consuming adaptors and Iterator adaptors

The concept of having a lazy description of transformation over data structures and the action that executes the description is also part of iterators in Rust. In Rust, the methods that leads to lazy transformations are called Iterator adaptors, while the once that perform the transformation and results a value are called Consuming adaptors. Same thang different day you might say!

Example of the Iterator adaptors include methods like filter(),  map() while examples of consuming adaptors are sum(), count() etc.

A new syntax was also dropped in this chapter. It was referred to as associated types, but I won't be knowing what these are until chapter 19 :)

That is it for now, next chapter would be about Cargo and so I expect that to be a breeze!