pierd.github.io


Project maintained by pierd Hosted on GitHub Pages — Theme by mattgraham
17 September 2018

Rust <3

This is my second approach to learn Rust and I think now I am starting to get it. That is I am starting to really like Rust.

The good

Cargo

Cargo is great. Simply great. It might actually start a new cult.

null is dead! Long live Option!

I really like that there is no null in Rust. When a value can be missing then one has to use Option. It makes the code more explicit because one has to “unpack” the real value which forces to perform the null check (a bit similar to how nullability is made explicit in Kotlin).

Pattern Matching and Enums

What makes Option (and Result) such a great solution is how Rust does pattern matching and enums. Go read about it!

Type conversions

Another great thing about Rust. It does not have type conversions on the language level, instead it has them implemented in standard library as Into and From traits.

One implements From for new types to mark as they can be created from given things aka values of this type can be created From X, Y, Z.

And then uses Into either as a type for function arguments (this function accepts all types that can be converted Into X) or calls .into() when passing an argument to make it Into what the function accepts.

Minimal language

As with the type conversions, the language is kept really small. For example it doesn’t have random number generation! It used to be a part of standard library but was deprecated and then removed. Now it lives in a crate.

The bad

Rust is hard and I mean hard. I have been programming more than a half of my life and using some crazy languages but programming in Rust brings me back to the beginnings.

That said it is getting better. Compiler is a friend and tries to help (and not just vomits pages of resolved templates). It actually has a page for each error with explanation what might have caused it and how to potentially fix it - teaching you Rust as you go.

The ugly

The borrow checker again. Take a look at this code. Nothing suspicious right?

fn main() {
    let mut vec = vec![1, 2, 3];
    vec.push(vec.len());
}

Well, let’s give it a go:

$ rustc double_borrow.rs
error[E0502]: cannot borrow `vec` as immutable because it is also borrowed as mutable
 --> double_borrow.rs:3:14
  |
3 |     vec.push(vec.len());
  |     ---      ^^^      - mutable borrow ends here
  |     |        |
  |     |        immutable borrow occurs here
  |     mutable borrow occurs here

error: aborting due to previous error

BOOM!

push is actually*:

pub fn push(&mut self, value: T)

So first there is a &mut borrow on vec to evaluate first argument and then another & borrow for calling len on the same vec. Mutable and immutable borrows at the same time - that’s a no-no in Rust land.

To fix this one can structure the code like this:

fn main() {
    let mut vec = vec![1, 2, 3];
    let len = vec.len();
    vec.push(len);
}

So the length is stored in a temporary variable and there is only one borrow at a time.

But why stop here? If you find yourself using this kind of construct all the time then you can use some other Rust magic. Another, more creative, solution would be to give each type a super power of pushing itself into a vector:

trait PushInto<T> {
    fn push_into(self, v: &mut Vec<T>);
}

impl <T> PushInto<T> for T {
    fn push_into(self, v: &mut Vec<T>) {
        v.push(self)
    }
}

fn main() {
    let mut vec = vec![1, 2, 3];
    vec.len().push_into(&mut vec);
}

Again only one borrow is done at a time.

It is not that bad as it sounds. The compiler is getting smarter and in Rust 2018 the first version of the code compiles without problems.

tags: rust