Add 03-bites-the-rust article
This commit is contained in:
parent
eb3244cf40
commit
559536db59
1 changed files with 668 additions and 0 deletions
668
content/post/03-bites-the-rust.md
Normal file
668
content/post/03-bites-the-rust.md
Normal file
|
@ -0,0 +1,668 @@
|
||||||
|
---
|
||||||
|
title: "Another one bites the Rust"
|
||||||
|
subtitle: "Lessons learned from my first Rust project"
|
||||||
|
date: 2019-08-25
|
||||||
|
draft: false
|
||||||
|
tags: [dev, programming, rust]
|
||||||
|
---
|
||||||
|
|
||||||
|
# Rust, the (re)discovery 🗺️
|
||||||
|
|
||||||
|
Back to **2014**, the year I wrote my first _Rust_ program ! Wow, that was
|
||||||
|
even **before** version 1.0 !
|
||||||
|
|
||||||
|
At my school, it was all about _C_, and 10 years of _C_ is quite boring
|
||||||
|
(because, yes, I started programming way before my engineering school). I
|
||||||
|
wanted some fresh air, and even if I really liked _Python_ back to that time,
|
||||||
|
i'm more into **strongly typed** programming languages.
|
||||||
|
|
||||||
|
I can't remember exactly what was my first contact with _Rust_, maybe a blog
|
||||||
|
post or a reddit post, and my reaction was something like :
|
||||||
|
|
||||||
|
![Brain Explosion (gif)](https://media.giphy.com/media/xT0xeJpnrWC4XWblEk/giphy.gif)
|
||||||
|
|
||||||
|
Now, let's dig some reasons about why _Rust_ blows my mind.
|
||||||
|
|
||||||
|
_Rust_ is a programming langage focused on safety, and concurrency. It's
|
||||||
|
basically the modern replacement of C++, plus, a multi-paradigm approach to
|
||||||
|
system programming development. This langage was created and designed by
|
||||||
|
Graydon Hoare at Mozilla and used for the _Servo_ browser engine, now
|
||||||
|
embedded in _Mozilla Firefox_.
|
||||||
|
|
||||||
|
This system try to be as **memory safe** as possible :
|
||||||
|
|
||||||
|
- no null pointers
|
||||||
|
- no dandling pointers
|
||||||
|
- no data races
|
||||||
|
- values have to be initialized and used
|
||||||
|
- no need to free allocated data
|
||||||
|
|
||||||
|
Back in 2014, the first thing that come to my mind was :
|
||||||
|
|
||||||
|
Yeah, yeah, just yet another garbage collected langage
|
||||||
|
|
||||||
|
And I was wrong ! _Rust_ uses other mechanisms such as _ownership_,
|
||||||
|
_lifetimes_, _borrowing_, and the ultimate **borrow checker** to ensure that
|
||||||
|
memory is safe and freed only when data will not be used anywhere else in the
|
||||||
|
program (_ie_ : when _lifetime_ is over). This new memory management concepts
|
||||||
|
ensure _safety_ **AND** _speed_ since there is no overhead generated by a
|
||||||
|
garbage collection.
|
||||||
|
|
||||||
|
For me, as a young hardcore C programmer[^1], this was literally heaven. Less struggling
|
||||||
|
with `calloc`, `malloc`, `free` and `valgrind`, thanks god _Mozilla_ ! Bless me !
|
||||||
|
|
||||||
|
**But**, I dropped it in 2015. Why ? Because this was, at this time, far from perfect.
|
||||||
|
Struggling with all the new concepts was quite disturbing, add to that cryptic compilation
|
||||||
|
errors and you open a gate to _brainhell_. My young me was not enough confident to learn
|
||||||
|
and there was no community to help me understand things that was unclear to me.
|
||||||
|
|
||||||
|
Five years later, my programming skills are clearly not at the same level as
|
||||||
|
before, learning and writing a lot of _Golang_ and _Javascript_ stuff, playing
|
||||||
|
with _Elixir_ and _Haskell_, completely changed how I manipulate and how I
|
||||||
|
visualize code in day to day basis. It was time to **give another** chance to _Rust_.
|
||||||
|
|
||||||
|
# Fediwatcher 📊
|
||||||
|
|
||||||
|
In order to practice my _Rust_ skill, I wanted to build a concrete and
|
||||||
|
useful _(at least for me)_ project.
|
||||||
|
|
||||||
|
Inspired by the work of **href** on
|
||||||
|
[fediverse.network](https://fediverse.network) my main idea was to build a
|
||||||
|
small app to fetch metrics from various instances of the **fediverse** and
|
||||||
|
push it into an [InfluxDB](https://influxdata.com) timeseries database.
|
||||||
|
|
||||||
|
**Fediwatcher** was born !
|
||||||
|
|
||||||
|
The code is available on a [github repo](https://github.com/papey/fediwatcher)
|
||||||
|
associated with my [github account](https://github.com/papey).
|
||||||
|
|
||||||
|
If you're interested, check out the [Fediwatcher public instance](https://metrics.papey.fr).
|
||||||
|
|
||||||
|
Ok, ok, enough personal promotion, now that you get the main idea, go for
|
||||||
|
the technical part and all the lessons learn writing this small project !
|
||||||
|
|
||||||
|
# Cargo, compiling & building 🏗️
|
||||||
|
|
||||||
|
`cargo` is the _Rust_ **standard** packet manager created and maintained by
|
||||||
|
the _Rust_ project. This is the tool used by all rustaceans and that's a good
|
||||||
|
thing. If you don't know it yet, I'm also a gopher, and package management
|
||||||
|
with `go` is a big pile of shit. In 2019, leaving package management to the
|
||||||
|
community is, I think, the biggest mistake you can make when creating a new
|
||||||
|
programming language. _So go for cargo !_
|
||||||
|
|
||||||
|
`cargo` is used for :
|
||||||
|
|
||||||
|
- downloading app dependencies
|
||||||
|
- compiling app dependencies
|
||||||
|
- compiling your project
|
||||||
|
- running your project
|
||||||
|
- running tests on your project
|
||||||
|
- publishing you project to [crates.io](https://crates.io)
|
||||||
|
|
||||||
|
All the informations are contained in the `Cargo.toml` file, no need for a
|
||||||
|
`makefile` makes my brain happy and having a common way to tests code without
|
||||||
|
external packages is pretty straightforward and a strong invitation to test
|
||||||
|
your code.
|
||||||
|
|
||||||
|
All the standard stuff also means that default docker images contains all you
|
||||||
|
need to setup a _Continous Integration_ without the need to maintain specific
|
||||||
|
container images for a specific test suite or whatever. Less time building
|
||||||
|
stuff, means more productive time to code. With _Rust_, all the batteries are
|
||||||
|
included.
|
||||||
|
|
||||||
|
About compiling, spoiler alert, _Rust_ project compiling **IS SLOW** :
|
||||||
|
|
||||||
|
{{< highlight sh >}}
|
||||||
|
cargo clean
|
||||||
|
time cargo build
|
||||||
|
Compiling autocfg v0.1.4
|
||||||
|
Compiling libc v0.2.58
|
||||||
|
Compiling arrayvec v0.4.10
|
||||||
|
Compiling spin v0.5.0
|
||||||
|
Compiling proc-macro2 v0.4.30
|
||||||
|
[...]
|
||||||
|
Compiling reqwest v0.9.18
|
||||||
|
Compiling fediwatcher v0.1.0 (/Users/wilfried/code/github/fediwatcher)
|
||||||
|
Finished dev [unoptimized + debuginfo] target(s) in 4m 25s
|
||||||
|
cargo build 571,39s user 50,53s system 233% cpu 4:25,95 total
|
||||||
|
{{< / highlight >}}
|
||||||
|
|
||||||
|
This is quite surprising if you compare it to a fast compiling language like `go`,
|
||||||
|
but that's fair because the compiler have to check a bunch of things related to
|
||||||
|
memory safety. With no garbage collector, speed at runtime and memory safety,
|
||||||
|
you have to pay a price and this price is the **compile time**.
|
||||||
|
|
||||||
|
But I really think it's not a weakness for _Rust_ because `cargo` caching is
|
||||||
|
amazing and after the first compilation, iterations are pretty fast so it's
|
||||||
|
not a real issue.
|
||||||
|
|
||||||
|
When it comes to building a _Docker_ image, I learned a nice tip to optimize
|
||||||
|
container image building with a clever use of layers. Here is the tip !
|
||||||
|
|
||||||
|
{{< highlight dockerfile "linenos=table" >}}
|
||||||
|
|
||||||
|
# New empty project
|
||||||
|
|
||||||
|
RUN USER=root cargo new --bin fediwatcher
|
||||||
|
WORKDIR /fediwatcher
|
||||||
|
|
||||||
|
# Fetch deps list
|
||||||
|
|
||||||
|
COPY ./Cargo.lock ./Cargo.lock
|
||||||
|
COPY ./Cargo.toml ./Cargo.toml
|
||||||
|
|
||||||
|
# Step to build a default hello world project.
|
||||||
|
# Since Cargo.lock and Cargo.toml are present,
|
||||||
|
# all deps will be downloaded and cached inside this upper layer
|
||||||
|
|
||||||
|
RUN cargo build --release
|
||||||
|
RUN rm src/\*.rs
|
||||||
|
|
||||||
|
# Now, copy source code
|
||||||
|
|
||||||
|
COPY ./src ./src
|
||||||
|
|
||||||
|
# Build the real project
|
||||||
|
|
||||||
|
RUN rm ./target/release/deps/fediwatcher\*
|
||||||
|
RUN cargo build --release
|
||||||
|
{{< / highlight >}}
|
||||||
|
|
||||||
|
Remember that dependencies are less volatile than code, and with containers
|
||||||
|
this means get dependencies as soon as possible and copy code later ! In the
|
||||||
|
_Rust_ case, the first thing to do is creating an empty project using `cargo new`.
|
||||||
|
This will create a default project with a basic hello world in
|
||||||
|
`main.rs` file.
|
||||||
|
|
||||||
|
After that, copy all things related to dependencies
|
||||||
|
(`Cargo.toml` and `Cargo.lock` files) and trigger a build, in this image
|
||||||
|
layer, all the deps will be downloaded and compiled.
|
||||||
|
|
||||||
|
Now that there is a layer
|
||||||
|
containing all the dependencies, copy the real source code and then compile
|
||||||
|
the real project. With this technique, the dependencies layer will be cached and used
|
||||||
|
in later build. Believe me, this a time saver !
|
||||||
|
|
||||||
|
Not lost yet ? Good, because there is more, so take a deep breath and go
|
||||||
|
digging some _Rust_ features.
|
||||||
|
|
||||||
|
# Flow control 🛂
|
||||||
|
|
||||||
|
_Rust_ takes inspiration from various programming language, mainly _C++_ a
|
||||||
|
imperative language, but there is also a lot of features that are typical in
|
||||||
|
_functional programming_. I already write some _Haskell_ (because
|
||||||
|
**Xmonad** ftw) and some _Elixir_ but I don't feel very confident with
|
||||||
|
functional programming yet.
|
||||||
|
|
||||||
|
I find this salad mix called as multi-paradigm
|
||||||
|
programming very convenient to understand and try some functional way of
|
||||||
|
thinking.
|
||||||
|
|
||||||
|
The top most functional feature of rust is the `match` statement. To me,
|
||||||
|
this the most beautiful and clean way to handle multiple paths inside a
|
||||||
|
program. For imperative programmers out there, a `match` is like a `switch case` on steroids.
|
||||||
|
To illustrate, let's look at a simple example[^2].
|
||||||
|
|
||||||
|
{{< highlight rust >}}
|
||||||
|
let number = 2;
|
||||||
|
|
||||||
|
println!("Tell me something about {}", number);
|
||||||
|
|
||||||
|
match number {
|
||||||
|
// Match a single value
|
||||||
|
1 => println!("One!"),
|
||||||
|
// Match several values
|
||||||
|
2 | 3 | 5 | 7 | 11 => println!("This is a prime"),
|
||||||
|
// Match an inclusive range
|
||||||
|
13..=19 => println!("A teen"),
|
||||||
|
// Whatever
|
||||||
|
_ => println!("Ain't special"),
|
||||||
|
}
|
||||||
|
{{< / highlight >}}
|
||||||
|
|
||||||
|
Here, all the cases are matched, but what if I removed the last branch ?
|
||||||
|
|
||||||
|
{{< highlight txt >}}
|
||||||
|
help: ensure that all possible cases are being handled, possibly by adding
|
||||||
|
wildcards or more match arms
|
||||||
|
{{< / highlight >}}
|
||||||
|
|
||||||
|
See ? _Rust_ violently pointing out missing stuff, and that's why it's a
|
||||||
|
pleasant language to use.
|
||||||
|
|
||||||
|
A `match` statement can also be used to _destructure_ a variable, a common
|
||||||
|
pattern in _functional_ programming. Destructuring is a process used to
|
||||||
|
break a structure into multiple and independent variables. This can also be
|
||||||
|
useful when you need only a part of a structure, making your code more
|
||||||
|
comprehensive and readable[^3].
|
||||||
|
|
||||||
|
{{< highlight rust >}}
|
||||||
|
struct Foo {
|
||||||
|
x: (u32, u32),
|
||||||
|
y: u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Try changing the values in the struct to see what happens
|
||||||
|
let foo = Foo { x: (1, 2), y: 3 };
|
||||||
|
|
||||||
|
match foo {
|
||||||
|
Foo { x: (1, b), y } => println!("First of x is 1, b = {}, y = {} ", b, y),
|
||||||
|
|
||||||
|
// you can destructure structs and rename the variables,
|
||||||
|
// the order is not important
|
||||||
|
Foo { y: 2, x: i } => println!("y is 2, i = {:?}", i),
|
||||||
|
|
||||||
|
// and you can also ignore some variables:
|
||||||
|
Foo { y, .. } => println!("y = {}, we don't care about x", y),
|
||||||
|
// this will give an error: pattern does not mention field `x`
|
||||||
|
//Foo { y } => println!("y = {}", y);
|
||||||
|
}
|
||||||
|
{{< / highlight >}}
|
||||||
|
|
||||||
|
With `match`, I made my first step inside the _functional programming_ way
|
||||||
|
of thinking. The second one was iterators, functions chaining and
|
||||||
|
closures, the perfect combo ! The idea is to chain function and pass input
|
||||||
|
and output from one to another. Chaining using small scope functions made
|
||||||
|
code more redable, more testable and more reliable. As always, an example !
|
||||||
|
|
||||||
|
{{< highlight rust >}}
|
||||||
|
let iterator = [1,2,3,4,5].iter();
|
||||||
|
|
||||||
|
// fold, is also known as reduce, in other languages
|
||||||
|
let sum = iterator.fold(0, |total, next| total + next);
|
||||||
|
|
||||||
|
println!("{}", sum);
|
||||||
|
{{< / highlight >}}
|
||||||
|
|
||||||
|
The first line is used to create a `iterator`, a structure used to perform
|
||||||
|
tasks on a sequence of items. Later on, a specific method associated with
|
||||||
|
iterators `fold` is used to sum up all items inside the iterator and produce
|
||||||
|
a single final value : the sum. As a parameter, we pass a `closure` (a
|
||||||
|
function defined on the fly) with a `total` and a `next` arguments. The
|
||||||
|
`total` variable is used to store current count status and `next` is the
|
||||||
|
next value inside the iterator to add to `total`.
|
||||||
|
|
||||||
|
A non functional alternative as the code shown
|
||||||
|
above will be something like :
|
||||||
|
|
||||||
|
{{< highlight rust >}}
|
||||||
|
let collection = [1,2,3,4,5];
|
||||||
|
|
||||||
|
let mut sum = 0;
|
||||||
|
|
||||||
|
for elem in collection.iter() {
|
||||||
|
sum += elem;
|
||||||
|
}
|
||||||
|
|
||||||
|
println!("{}", sum);
|
||||||
|
{{< / highlight >}}
|
||||||
|
|
||||||
|
With more complex data, more operations, removing for loops and chaining
|
||||||
|
function using `map`, `filter` or `fold` really makes code cleaner and easier
|
||||||
|
to understand. You just get important stuff, there is no distraction and
|
||||||
|
a code without boiler plate lines is less error probes.
|
||||||
|
|
||||||
|
Flow control is a large domain and it contains error handling. In _Rust_
|
||||||
|
there is two kind of generic errors : `Option` used to describe the
|
||||||
|
possibility of _absence_ and `Result` used as supersed of `Option` to handle
|
||||||
|
the possibility of errors.
|
||||||
|
|
||||||
|
Here is the definition of an `Option` :
|
||||||
|
|
||||||
|
{{< highlight rust >}}
|
||||||
|
enum Option<T> {
|
||||||
|
None,
|
||||||
|
Some(T),
|
||||||
|
}
|
||||||
|
{{< / highlight >}}
|
||||||
|
|
||||||
|
Where `None` means "no value" and `Some(T)` means "some variable (of type `T`)"
|
||||||
|
|
||||||
|
An `Option` is useful if you, for example, search for a file that may not exists
|
||||||
|
|
||||||
|
{{< highlight rust >}}
|
||||||
|
let file = "not.exists";
|
||||||
|
|
||||||
|
match find(file, '.') {
|
||||||
|
None => println!("File not found."),
|
||||||
|
Some(i) => println!("File found : {}", &file),
|
||||||
|
}
|
||||||
|
{{< / highlight >}}
|
||||||
|
|
||||||
|
If you need an explicit error to handle, go for `Result` :
|
||||||
|
|
||||||
|
{{< highlight rust >}}
|
||||||
|
enum Result<T, E> {
|
||||||
|
Ok(T),
|
||||||
|
Err(E),
|
||||||
|
}
|
||||||
|
{{< / highlight >}}
|
||||||
|
|
||||||
|
Where `Ok(T)` means "everything is good for the value (of type `T`)" and
|
||||||
|
`Err(E)` means "An error (of type `E`) occurs". To conclude, it's possible to
|
||||||
|
define an `Option` like this :
|
||||||
|
|
||||||
|
{{< highlight rust >}}
|
||||||
|
type Option<T> = Result<T, ()>;
|
||||||
|
{{< / highlight >}}
|
||||||
|
|
||||||
|
"An `Option` is a `Result` with an empty `Err` value". Q.E.D !
|
||||||
|
|
||||||
|
At this point of my journey (re)discovering _Rust_ I was super happy with all
|
||||||
|
this new concepts. As a gopher, I know how crappy error handling can be in
|
||||||
|
other languages, so a clean and standard way to handle error, count me in.
|
||||||
|
|
||||||
|
So, what about composing functions that needs error handling ? Ahah ! Let's
|
||||||
|
go :
|
||||||
|
|
||||||
|
{{< highlight rust "linenos=table, hl_lines=32-40 43-45 48-50" >}}
|
||||||
|
// An example using music bands
|
||||||
|
|
||||||
|
// Allow dead code, for learning purpose
|
||||||
|
#![allow(dead_code)]
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
enum Bands {
|
||||||
|
AAL,
|
||||||
|
Alcest,
|
||||||
|
Sabaton,
|
||||||
|
}
|
||||||
|
|
||||||
|
// But does it djent ?
|
||||||
|
fn does_it_djent(b: Bands) -> Option<Bands> {
|
||||||
|
match b {
|
||||||
|
// Only Animals As Leaders djents
|
||||||
|
Bands::AAL => Some(b),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Do I like it ?
|
||||||
|
fn likes(b: Bands) -> Option<Bands> {
|
||||||
|
// No, I do not like Sabaton
|
||||||
|
match b {
|
||||||
|
Bands::Sabaton => None,
|
||||||
|
_ => Some(b),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Do it djent and do I like it ? the match version !
|
||||||
|
fn match_likes_djent(b: Bands) -> Option<Bands> {
|
||||||
|
match does_it_djent(b) {
|
||||||
|
Some(b) => match likes(b) {
|
||||||
|
Some(b) => Some(b),
|
||||||
|
None => None,
|
||||||
|
},
|
||||||
|
None => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Do it djent and do I like it ? the map version !
|
||||||
|
fn map_likes_djent(b: Bands) -> Option<Option<Bands>> {
|
||||||
|
does_it_djent(b).map(|b| likes(b))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Do it djents and do I like it ? the and_then version !
|
||||||
|
fn and_then_likes_djent(b: Bands) -> Option<Bands> {
|
||||||
|
does_it_djent(b).and_then(|b| likes(b))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
let aal = Bands::AAL;
|
||||||
|
|
||||||
|
match and_then_likes_djent(aal) {
|
||||||
|
Some(b) => println!("I like {:?} and it djents", b),
|
||||||
|
None => println!("Hurgh, this band doesn't even djent !"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
{{< / highlight >}}
|
||||||
|
|
||||||
|
On a first try, the basic solution is to use a series of `match` statements
|
||||||
|
(line 32). With two functions, that's ok, but with 3 or more, this
|
||||||
|
will be a pain in the ass to read. Searching for a cleaner way of handling
|
||||||
|
stuff that returns an `Option` I find the associated `map` method. **BUT**
|
||||||
|
using `map` with something that also return an `Option` leads to (function
|
||||||
|
definition on line 43) :
|
||||||
|
|
||||||
|
**an Option of an Option !**
|
||||||
|
|
||||||
|
![Facepalm](https://upload.wikimedia.org/wikipedia/commons/3/3b/Paris_Tuileries_Garden_Facepalm_statue.jpg)
|
||||||
|
|
||||||
|
Is everything doomed ? No ! Because there is the god send `and_then` method
|
||||||
|
(function starting on line 48). Basically, `and_then` ensure that we keep a
|
||||||
|
"flat" structure and do not add an `Option` wrapping to an already existing
|
||||||
|
`Option`. _Lesson learned_ : if you have to deal with a lot of `Option`s or
|
||||||
|
`Result`s, use `and_then`.
|
||||||
|
|
||||||
|
Last but not least, I also want to write about the `?` operator for error
|
||||||
|
handling. Since _Rust_ version 1.13, this new operator removes a lot of
|
||||||
|
boiler plate and redundant code.
|
||||||
|
|
||||||
|
Before 1.13, error handling will probably look like this :
|
||||||
|
|
||||||
|
{{< highlight rust >}}
|
||||||
|
fn read_from_file() -> Result<String, io::Error> {
|
||||||
|
let f = File::open("sample.txt");
|
||||||
|
let mut s = String::new();
|
||||||
|
|
||||||
|
let mut f = match f {
|
||||||
|
Ok(f) => f
|
||||||
|
Err(e) => return Err(e),
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
match f.read_to_string(&mut s) {
|
||||||
|
Ok(_) => Ok(s),
|
||||||
|
Err(e) => Err(e),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
{{< / highlight >}}
|
||||||
|
|
||||||
|
With 1.13 and later,
|
||||||
|
|
||||||
|
{{< highlight rust >}}
|
||||||
|
fn read_from_file() -> Result<String, io::Error> {
|
||||||
|
let mut s = String::new();
|
||||||
|
let mut f = File::open("sample.txt")?;
|
||||||
|
|
||||||
|
f.read_to_string(&mut s)?;
|
||||||
|
|
||||||
|
Ok(s)
|
||||||
|
}
|
||||||
|
{{< / highlight >}}
|
||||||
|
|
||||||
|
Nice and clean ! _Rust_ also experiment with a function name `try`, used like
|
||||||
|
the `?` operator, but chaining functions leads to unreadable and ugly code :
|
||||||
|
|
||||||
|
{{< highlight rust >}}
|
||||||
|
try!(try!(try!(foo()).bar()).baz())
|
||||||
|
{{< / highlight >}}
|
||||||
|
|
||||||
|
To conclude, there is a lot of stuff here, to make code easy to understand
|
||||||
|
and maintain. Flow control using match and functions combination may seems
|
||||||
|
odd at the beggining but after some pratice and experiments I find quite
|
||||||
|
pleasant to use. But there is (again), more, fasten your seatbelt, next
|
||||||
|
section will blow your mind.
|
||||||
|
|
||||||
|
# Ownership, borrowing, lifetimes 🤯
|
||||||
|
|
||||||
|
To be clear, the 10 first hours of _Rust_ coding just smash my brains because
|
||||||
|
of this three concepts. They are quite handy to understand at first, because
|
||||||
|
they change the way we make and understand programs. With time, pratice and
|
||||||
|
compiler help, the mist is replaced by a beautiful sunligth. There is plenty
|
||||||
|
of other blog posts, tutorials and lessons about lifetimes, ownership and
|
||||||
|
borrowing. I will add my brick to this wall, with my own understanding of it.
|
||||||
|
|
||||||
|
Let's start with **ownership**. _Rust_ memory management is base on this
|
||||||
|
concept. Every resources (variables, objects...) is **own** by a block of
|
||||||
|
code. At the end of this block, resourses are destroyed. This is the standard
|
||||||
|
predicatable, reproducible, behavior of _Rust_. For small stuff,
|
||||||
|
that's easy to understand :
|
||||||
|
|
||||||
|
{{< highlight rust "linenos=table, hl_lines=11">}}
|
||||||
|
fn main() {
|
||||||
|
// create a block, or scope
|
||||||
|
{
|
||||||
|
// resource creation
|
||||||
|
let i = 42;
|
||||||
|
println!("{}", i);
|
||||||
|
// i is destroyed by the compiler, and you have nothing else to do
|
||||||
|
}
|
||||||
|
|
||||||
|
// fail, because i do not exists anymore
|
||||||
|
println!("{}", i);
|
||||||
|
|
||||||
|
}
|
||||||
|
{{< / highlight >}}
|
||||||
|
|
||||||
|
Compiling this piece of code will throw an error :
|
||||||
|
|
||||||
|
{{< highlight txt >}}
|
||||||
|
error[E0425]: cannot find value `i` in this scope
|
||||||
|
--> src/main.rs:11:20
|
||||||
|
|
|
||||||
|
11 | println!("{}", i);
|
||||||
|
| ^ not found in this scope
|
||||||
|
{{< / highlight >}}
|
||||||
|
|
||||||
|
To remove the error, just delete the line 11.
|
||||||
|
|
||||||
|
Ok, cool ! But what if I want to pass a resources to another block or even a
|
||||||
|
function ?
|
||||||
|
|
||||||
|
{{< highlight rust "linenos=table" >}}
|
||||||
|
fn priprint(val: int) {
|
||||||
|
println!("{}", val);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
let i = 42;
|
||||||
|
|
||||||
|
priprint(i);
|
||||||
|
|
||||||
|
println!("{}", i);
|
||||||
|
|
||||||
|
}
|
||||||
|
{{< / highlight >}}
|
||||||
|
|
||||||
|
Here, this piece of code works because _Rust_ copy the value of `i` into
|
||||||
|
`val` when calling the `priprint` function. All primitve type in _Rust_
|
||||||
|
works this way, but, if you want to pass, for example, a struct, _Rust_
|
||||||
|
will **move** the resource to the function. By **moving** a resource, you
|
||||||
|
**transfer** ownership to the receiver. So in the example below `priprint`
|
||||||
|
will be responsible of the destruction of the struct passed to it.
|
||||||
|
|
||||||
|
{{< highlight rust "linenos=table, hl_lines=12" >}}
|
||||||
|
struct Number {
|
||||||
|
value: i32
|
||||||
|
}
|
||||||
|
|
||||||
|
fn priprint(n: Number) {
|
||||||
|
println!("{}", n.value);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
let n = Number{ value: 42 };
|
||||||
|
|
||||||
|
priprint(n);
|
||||||
|
|
||||||
|
println!("{}", n.value);
|
||||||
|
|
||||||
|
}
|
||||||
|
{{< / highlight >}}
|
||||||
|
|
||||||
|
When compiling, _Rust_ will not be happy :
|
||||||
|
|
||||||
|
{{< highlight txt >}}
|
||||||
|
error[E0382]: borrow of moved value: `n`
|
||||||
|
--> src/main.rs:14:20
|
||||||
|
|
|
||||||
|
10 | let n = Number{ value: 42 };
|
||||||
|
| - move occurs because `n` has type `Number`, which does not implement the `Copy` trait
|
||||||
|
11 |
|
||||||
|
12 | priprint(n);
|
||||||
|
| - value moved here
|
||||||
|
13 |
|
||||||
|
14 | println!("{}", n.value);
|
||||||
|
| ^^^^^^^ value borrowed here after move
|
||||||
|
{{< / highlight >}}
|
||||||
|
|
||||||
|
After **ownership** comes **borrowing**. With **borrowing** our _Rust_
|
||||||
|
program is able to have multiple references or _pointers_. Passing a
|
||||||
|
reference to another block tells to this block, here is a **borrow** (mutable
|
||||||
|
or imutable) do what you want with it but do not destroy it at the end of your
|
||||||
|
scope. To pass references, or **borrows**, add the `&` operator to `priprint`
|
||||||
|
argument and parameter.
|
||||||
|
|
||||||
|
{{< highlight rust "linenos=table, hl_lines=5 12" >}}
|
||||||
|
struct Number {
|
||||||
|
value: i32
|
||||||
|
}
|
||||||
|
|
||||||
|
fn priprint(n: &Number) {
|
||||||
|
println!("{}", n.value);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
let n = Number{ value: 42 };
|
||||||
|
|
||||||
|
priprint(&n);
|
||||||
|
|
||||||
|
println!("{}", n.value);
|
||||||
|
|
||||||
|
}
|
||||||
|
{{< / highlight >}}
|
||||||
|
|
||||||
|
Seems cool no ? If a friend of mine borrow my car, I hope he will not
|
||||||
|
return it in pieces.
|
||||||
|
|
||||||
|
Now, **lifetimes** ! _Rust_ resources always have a **lifetime** associated
|
||||||
|
to it. This means that resources the are accessible or "live" from the moment you
|
||||||
|
declare it and the moment they are dropped. If you're familiar with other
|
||||||
|
programming languages, think about **extensible scopes**. To me **extensible
|
||||||
|
scopes** means that **scopes** can be move from one block of code to another. Simple, huh ? But
|
||||||
|
things get complicated if you add references in the mix. Why ? Because
|
||||||
|
references also have **lifetime**, and this **lifetime**, called **associated
|
||||||
|
lifetime**, can be smaller than the **lifetime** pointed by the reference. Can
|
||||||
|
this **associated lifetime** be longer ? No ! Because we want to access valid
|
||||||
|
data ! In most cases, _Rust_ compiler is able to guess how **lifetimes** are
|
||||||
|
related. If not, it will explicitly ask you to annotate you're code with
|
||||||
|
**lifetimes specifiers**. To dig this subject, a whole article is necessary and
|
||||||
|
I don't find my self enough confident with **lifetimes** yet to explain it
|
||||||
|
in details. This is clearly the hardest part when you learning _Rust_. If
|
||||||
|
you don't understand what you're doing at the beginning, that's not a real problem.
|
||||||
|
Don't give up, read, try and experiment, the reward worth it.
|
||||||
|
|
||||||
|
![No idea](https://i.kym-cdn.com/entries/icons/original/000/008/342/ihave.jpg)
|
||||||
|
|
||||||
|
# What's next ? 🔭
|
||||||
|
|
||||||
|
Thanks to _Rust_ and my little project, I learned a bunch of new concepts
|
||||||
|
related to programming.
|
||||||
|
|
||||||
|
_Rust_ is a beautiful language. The first time I used it, many years ago, it
|
||||||
|
was a bit odd to understand. Today, with more programming experiences, I
|
||||||
|
really understand why it matters. To me 2019, will be the _Rust_ year. A lots
|
||||||
|
of _Rust_ projects pops up on Github, and that's a good sign of how the
|
||||||
|
language start to gain in popularity. Backed up with Mozilla and the
|
||||||
|
community, I really believe that's it's the go to language for the next 10
|
||||||
|
years. Of course, _Golang_ is also in this spectrum of new generation
|
||||||
|
laguages but they complement one each other with various ways of thinking and
|
||||||
|
programming. That's clear to me, I will continue to make _Go_ **AND** _Rust_
|
||||||
|
programs.
|
||||||
|
|
||||||
|
Now, I need to go deeper. On one hand, by adding new features to
|
||||||
|
**Fediwatcher** I want to experiment around concurrency and how I can
|
||||||
|
compare it to _Golang_.
|
||||||
|
|
||||||
|
On the other hand, I'm really, really interested by **web assembly** and I
|
||||||
|
think _Rust_ is a good bet to start exploring this new open field. Last but not
|
||||||
|
least, all this skills will allow me to continue my contributions to
|
||||||
|
[Plume](https://github.com/Plume-org/Plume), a _Rust_ federated blogging
|
||||||
|
application, based on ActivityPub.
|
||||||
|
|
||||||
|
Let's go^Wrust !
|
||||||
|
|
||||||
|
[^1]: I am not a C hardcore programmer anymore, beceause of _Golang_ and _Rust_, of course.
|
||||||
|
[^2]: Taken and adapted from [Rust documentation](https://doc.rust-lang.org/rust-by-example/flow_control/match.html)
|
||||||
|
[^3]: Taken from [Rust documentation](https://doc.rust-lang.org/rust-by-example/flow_control/match/destructuring/destructure_structures.html)
|
Loading…
Reference in a new issue