My first cup of Rust
Tags: rust, beginners, learning
Published: 2021-05-30 16:09:35

It all started with an informal chat with my friend [Anthony](https://twitter.com/anthonydahanne). We were talking about languages, and I said that I preferred compiled ones. He then went on to mention Rust. We admitted that we were too afraid to learn it because of its perceived complexity.

After the chat, I thought about it, and I wondered why I didn't check by myself. I did. And I became interested.

There are tons of resources on the Web on how to learn Rust. This post is a memo of the steps I followed. It's probably less structured than my other posts but I hope you might benefit from those notes anyway.

## Rust entry-point

The main entry-point for learning Rust is the [official Rust book](https://doc.rust-lang.org/book/).

In the book, the installation method is with `curl`. IMHO, this approach has two downsides:

1. It's a security risk, however slight
2. More importantly, no package manager can automatically update the installation.

Fortunately, a Homebrew package exists. It's a two-step process:

```bash
brew install rustup-init   // 1
rustup-init                // 2
```

1. Install the package
2. Launch the proper intialization process

It outputs the following:

```
Welcome to Rust!

This will download and install the official compiler for the Rust
programming language, and its package manager, Cargo.

*** Logs, logs and more logs ***

Rust is installed now. Great!

To get started you need Cargo's bin directory ($HOME/.cargo/bin) in your PATH
environment variable. Next time you log in this will be done
automatically.

To configure your current shell, run:
source $HOME/.cargo/env
```

## Create a new project

As a "modern" language, Rust provides a lot of different features beyond the syntax. Among them is the ability to create projects via the `cargo` command based on a template.

```bash
cargo new start_rust
```

Alternatively, your favorite IDE probably has a Rust plugin. For example, JetBrains provides a [one](https://plugins.jetbrains.com/plugin/8182-rust) for IntelliJ IDEA.

Whatever the chosen approach, the project is under source control. It displays the following structure:

```
/___
|___ Cargo.toml     // 1
|___ .gitignore     // 2
|  |___ src
|     |___ main.rs  // 3
```

1. Project meta-data:
it includes dependencies
2. `.gitignore` configured for Rust
3. Code where the magic happens

To run the default project, use the `cargo` command inside the project's folder:

```bash
cargo run
```

It starts compilation and then executes the application:

```
   Compiling start_rust v0.1.0 (/Users/nico/projects/private/start_rust)
    Finished dev [unoptimized + debuginfo] target(s) in 1.05s
     Running `target/debug/start_rust`
Hello, world!
```

If you follow along, you might notice that a new `Cargo.lock` file has appeared after the first run. It plays the same role as Ruby's `Gemfile.lock` and npm's `package-lock.json`.

Now is the right time to inspect the source file. I believe that even if it's the first time you see Rust code, you can infer what it does:

```rust
fn main() {
    println!("Hello, world!");
}
```

## Let's set a goal

My hands-on learning approach requires a goal. It must not be too simple to learn something but not too hard, lest I become discouraged.

At first, I wanted to create a simple GUI application, the same I used to [explore JVM desktop frameworks](https://blog.frankel.ch/state-jvm-desktop-frameworks/2/#a-sample-application). But after reading about the [state of GUI frameworks in Rust](https://www.areweguiyet.com/), I decided against it. Instead, I chose to lower the bar and implement [one of the Java exams](https://github.com/formations/java-advanced-exam-2019) I had given to my students.

The exam defines a couple of model classes that serve as a foundation for the work. I did split the work into "exercise" classes. Each class contains a single method with either an empty body for `void` returning methods or a dummy return value for others. I wrote the instruction in each class: it's up to the student to write the implementation that returns the expected result.

Let's start small and define only part of the model.

![Starting the design of the model](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/4754e56ynzxz1j1pnqzj.png)

Here's how it translates naively into Rust:

```rust
pub struct Super {
    pub super_name: String,
    pub real_name: String,
    pub power: u16,
}

pub struct Group {
    pub name: String,
    pub members: Vec<Super>,
}
```

## Organizing one's code

In Java or Kotlin, we organize our code in packages. Rust has packages, but it has different semantics. It also offers crates and modules. Rust's modules are similar to Java's packages.

Here's the overview of a sample package:

![Organizing one's code](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/55jp9yyd80mtm510igfs.png)

I must admit the official documentation confused me. This [thread](https://www.reddit.com/r/rust/comments/lvtzri/confused_about_package_vs_crate_terminology/) is a pretty good explanation, <abbr title="In My Humbler Opinion">IMHO</abbr>. The above diagram represents my understanding of it; feel free to correct me if necessary.

Let's move the model-related structures to a dedicated `model` module in the `library` crate to understand better how it works. To achieve that, we need to create a `lib.rs` file - the common name - that defines the `model` module:

```rust
mod model;
```

Our project now consists of a binary and a library. The library crate contains one module that includes the model.

We will also create the functions to implement in a dedicated `solutions` module. Because `solutions` need to access the model, we need to set its visibility to `public`. And since we want to test functions in `solutions`, let's make it also public.

```rust
pub mod model;
pub mod solutions;
```

The current structure looks like this:

```
/___
   |___ src
      |___ lib.rs
      |___ main.rs
      |___ model.rs
      |___ solutions.rs
```

## Implementing our first function

The exercise `A` class requires that given a collection of `Group`, we should return the `Group` that has the most members, _i.e._, the largest group. Note that in the initial exam, `A` and `I` are similar. However, students should solve `A` by using "standard" Java for loop and `I` by using only Java streams.

Rust makes it easier to follow Functional Programming principles.

```rust
pub fn find_largest_group(groups: Vec<Group>) -> Option<&Group> {
    groups
        .iter()
        .max_by(|&g1, &g2| g1.members.len().partial_cmp(&g2.members.len()).unwrap())
}
```

This single implementation warrants many comments:

* The syntax of the closure is inspired by Ruby's
* By convention, the last _expression_ of a function is the returned value
* Rust considers that the function body is an expression because it doesn't end with a semicolon. If it did, it would be a statement and wouldn't qualify as a return value.
* The `&` character indicates a _reference_ instead of a value. It's akin to C and Go in that regard.
* `Vec` is a resizable collection with order and random access, similar to Java's `ArrayList`
* The `Option` type works like `Optional` in Java: it may contain a value, `Some` or not, `None`.
* The `max_by` is implemented by comparing the size of the `members` value of each group
* Rust implements `partial_cmp()` for each "primitive" type by default, including `u16`
* if `group` is empty, `iter()` directly returns `None` so we can safely call `unwrap()` in the closure

The code looks fine until we try to compile it:

```
error[E0106]: missing lifetime specifier
 --> src/solutions.rs:3:57
  |
3 | pub fn find_largest_group(groups: Vec<Group>) -> Option<&Group> {
  |                                                         ^ expected named lifetime parameter
  |
  = help: this function's return type contains a borrowed value with an elided lifetime, but the lifetime cannot be derived from the arguments
help: consider using the `'static` lifetime
  |
3 | pub fn find_largest_group(groups: Vec<Group>) -> Option<&'static Group> {
  |                                                         ^^^^^^^^
```

That's the start of Rust's fun.

I don't want to paraphrase a whole [section](https://doc.rust-lang.org/book/ch10-03-lifetime-syntax.html) of the Rust book. Suffice to say that references are valid in a specific scope, and when the latter cannot be inferred, we need to be explicit about it.

The _static_ lifetime mentioned in the hint means the reference will be valid until the end of the program. In most cases, that's not necessary. Instead, we should bind the lifetime of the returned value to the lifetime of the parameter. Lifetime hints look a lot like generics with the additional `'`.

Let's change the signature accordingly:

```rust
pub fn find_largest_group<'a>(groups: Vec<Group>) -> Option<&'a Group> {
    groups
        .iter()
        .max_by(|&g1, &g2| g1.members.len().partial_cmp(&g2.members.len()).unwrap())
}
```

Yes, the update code still doesn't compile:

```
error[E0515]: cannot return value referencing function parameter `groups`
  --> src/foo.rs:13:5
   |
13 |       groups
   |       ^``-
   |       |
   |  _____`groups` is borrowed here
   | |
14 | |         .iter()
15 | |         .max_by(|&g1, &g2| g1.members.len().partial_cmp(&g2.members.len()).unwrap())
   | |____________________________________________________________________________________^ returns a value referencing data owned by the current function
```

That's Rust's fun continued.

Because the function uses the `groups` parameter, it [borrows](https://doc.rust-lang.org/book/ch04-02-references-and-borrowing.html) it and becomes its owner. In our context, the function doesn't modify `groups`; hence borrowing is not necessary. To avoid it, we should use a reference instead and set the lifetime accordingly:

```rust
pub fn find_largest_group<'a>(groups: &'a Vec<Group>) -> Option<&'a Group> {
    groups
        .iter()
        .max_by(|&g1, &g2| g1.members.len().partial_cmp(&g2.members.len()).unwrap())
}
```

Now, the returned value has the same lifetime as the parameter. At this point, the compiler can correctly infer the lifetime, and we can remove all hints.

```rust
pub fn find_largest_group(groups: &Vec<Group>) -> Option<&Group> {
    groups
        .iter()
        .max_by(|&g1, &g2| g1.members.len().partial_cmp(&g2.members.len()).unwrap())
}
```

## Automated tests

A well-designed language should make testing easy. We can create a new dedicated module aptly named `tests` with each test case in a dedicated module.

```rust
pub mod model;     // 1
pub mod solutions;
mod tests;         // 2
```

1. Because tests will need to access the `model` module, we need to set it _public_
2. Create the `tests` module. Tests don't need to be public.

```rust
mod a;
mod b;
```

```
/___
   |___ src
      |___ lib.rs
      |___ main.rs
      |___ model.rs
      |___ solutions.rs
      |___ tests.rs
      |___ tests
          |___ a.rs
          |___ b.rs
```

For tests, we need to:

1. Add the `#[cfg(test)]` annotation to the file
2. And annotate each test function with `#[test]`

```rust
#[cfg(test)]                                      // 1

use crate::model::Group;                          // 2
use crate::solutions::a;                          // 2

#[test]                                           // 3
fn should_return_none_if_groups_is_empty() {
    let groups = Vec::new();
    let result = a::find_largest_group(&groups);  // 4
    assert!(result.is_none());                    // 5
}
```

1. Run this code only with `cargo test` and not with `cargo build`
2. Import paths. The Rust convention is to use structures' full path but to use functions' path last segment
3. Mark this function as a test
4. Call the method to test
5. Assert the result. The `!` hints at [a macro](https://doc.rust-lang.org/book/ch19-06-macros.html).

## Traits for the win

So far, so good.
Now, let's add a second more meaningful test method:

```rust
#[test]
fn should_return_group_if_groups_has_only_one() {
    let group = Group {
        name: "The Misfits of Science",
        members: Vec::new(),
    };
    let groups = vec![group];
    let result = a::find_largest_group(&groups);
    assert!(result.is_some());
    assert_eq!(result.unwrap(), &group);
}
```

It fails miserably with two errors:

```
error[E0369]: binary operation `==` cannot be applied to type `&Group`
  --> src/tests/a.rs:19:5
   |
19 |     assert_eq!(result.unwrap(), &group);
   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
   |     |
   |     &Group
   |     &Group
   |
   = note: an implementation of `std::cmp::PartialEq` might be missing for `&Group`
   = note: this error originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info)

error[E0277]: `Group` doesn't implement `Debug`
  --> src/tests/a.rs:19:5
   |
19 |     assert_eq!(result.unwrap(), &group);
   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ `Group` cannot be formatted using `{:?}`
   |
   = help: the trait `Debug` is not implemented for `Group`
   = note: add `#[derive(Debug)]` or manually implement `Debug`
   = note: required because of the requirements on the impl of `Debug` for `&Group`
   = note: 1 redundant requirements hidden
   = note: required because of the requirements on the impl of `Debug` for `&&Group`
   = note: required by `std::fmt::Debug::fmt`
   = note: this error originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info)
```

The compiler detects two errors. Let's handle the second one first. Something bad happens, and the compiler wants to print the structure in detail to help us understand. It needs the `Group` structure to implement the `Debug` _trait_. Rust's traits are similar to Scala's and Java's interfaces (with default implementations).

You can add a trait to a `struct` with the `derive` macro. It's a good idea to make both our model classes "debuggable":

```rust
#[derive(Debug)]
pub struct Super {
    // ...
}

#[derive(Debug)]
pub struct Group {
    // ...
}
```

The first error above tells that `Group` cannot be compared using `==` because the `PartialEq` trait is missing. It's tempting to add this trait to `Group`, but now the compiler throws a new error:

```
error[E0369]: binary operation `==` cannot be applied to type `Vec<Super>`
  --> src/model.rs:12:5
   |
12 |     pub members: Vec<Super>,
   |     ^^^^^^^^^^^^^^^^^^^^^^^
   |
   = note: this error originates in a derive macro (in Nightly builds, run with -Z macro-backtrace for more info)
```

To understand it, we need to look at the documentation of `PartialEq`:

> When `derived` on structs, two instances are equal if all fields are equal, and not equal if any fields are not equal.

A solution is to "override" the default trait implementation and decide what it means for two `Group` to be equal. A more accessible alternative is to add the trait to `Super` as all attributes of `Super` can then be compared using `PartialEq`.

## More ownership you'd ever want to know about

At this point, we would hope that the code compiles, but the compiler still complains:

```
error[E0382]: borrow of moved value: `group`
  --> src/tests/a.rs:19:33
   |
15 |     let group = Group { name: String::from("The Misfits of Science"), members: Vec::new() };
   |         ```- move occurs because `group` has type `Group`, which does not implement the `Copy` trait
16 |     let groups = vec![group];
   |                       ```- value moved here
...
19 |     assert_eq!(result.unwrap(), &group);
   |                                 ^^^^^^ value borrowed here after move
```

We talked briefly about ownership above: the error is related. In line 16, `groups` gets ownership of the `group` variable. As a consequence, we are not allowed to use `group` after that line.

Like all ownership-related compiler errors, the reason is pretty straightforward: `groups` could have been cleared so that `group` wouldn't exist anymore. We could try to copy/clone `group`, but that would be neither efficient nor idiomatic. Instead, we need to change our mindset, completely forget about `group` and use `groups`. The new version becomes and finally compiles:

```rust
#[test]
fn should_return_group_if_groups_has_only_one_element() {
    let group = Group {
        name: String::from("The Misfits of Science"),
        members: Vec::new(),
    };
    let groups = vec![group];
    let result = a::find_largest_group(&groups);
    assert!(result.is_some());
    assert_eq!(result, groups.first());
}
```

## Designing samples

The original Java project provides test samples so that students can quickly check their solutions. The idea is to create immutable instances that you can reuse across tests. For that, Rust provides the `const` keyword. Let's create a dedicated file for samples:

```rust
const JUSTICE_LEAGUE: Group = Group {
    name: String::from("Justice League"),
    members: vec![],
};
```

It fails with an error:

```
error[E0015]: calls in constants are limited to constant functions, tuple structs and tuple variants
 --> src/tests/samples.rs:4:11
  |
4 |     name: String::from("Justice League"),
  |           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
```

You might have wondered why the `String::from()` function call. By default, double quotes define a [string `slice` ](https://doc.rust-lang.org/book/ch04-03-slices.html) and not a proper `String`.

Since constants need to be initialized by a simple assignment, we cannot convert the type. We have to change our model class:

```rust
#[derive(Debug, PartialEq)]
pub struct Group {
    pub name: &str,
    pub members: Vec<Super>,
}
```

Compilation now fails with a now-familiar error:

```
error[E0106]: missing lifetime specifier
  --> src/model.rs:10:15
   |
10 |     pub name: &str,
   |               ^ expected named lifetime parameter
   |
help: consider introducing a named lifetime parameter
   |
9  | pub struct Group<'a> {
10 |     pub name: &'a str,
   |
```

We need to bind the lifetime of the attribute to the lifetime of its parent structure.

```rust
#[derive(Debug, PartialEq)]
pub struct Group<'a> {
    pub name: &'a str,
    pub members: Vec<Super>,
}
```

In turn, we also need to change the signature of the solution:

```rust
pub fn find_largest_group<'a>(groups: &'a Vec<Group<'a>>) -> Option<&'a Group<'a>> {
    groups
        .iter()
        .max_by(|&g1, &g2| g1.members.len().partial_cmp(&g2.members.len()).unwrap())
}
```

Likewise, we change the attributes' type for `Super` and again `Group`:

```rust
#[derive(Debug, PartialEq)]
pub struct Super<'a> {
    pub super_name: &'a str,
    pub real_name: &'a str,
    pub power: u16,
}

#[derive(Debug, PartialEq)]
pub struct Group<'a> {
    pub name: &'a str,
    pub members: Vec<Super<'a>>,
}
```

At this point, we can add `Super` constants and use them in `Group` constants:

```rust
pub const BATMAN: Super = Super {
    super_name: "Batman",
    real_name: "Bruce Wayne",
    power: 50,
};

pub const JUSTICE_LEAGUE: Group = Group {
    name: "Justice League",
    members: vec![SUPERMAN],
};
```

Compilation fails again... what a surprise, but this time for another reason:

```
error[E0010]: allocations are not allowed in constants
  --> src/tests/samples.rs:17:14
   |
17 |     members: vec![SUPERMAN, BATMAN],
   |              ^^^^^^^^^^^^^^^^^^^^^^ allocation not allowed in constants
   |
   = note: this error originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info)
```

It seems that constants are not a good fit for samples. Instead, we should implement functions to generate them. This way, ownership won't get in our way.

```rust
pub fn batman<'a>() -> Super<'a> {
    Super {
        super_name: "Batman",
        real_name: "Bruce Wayne",
        power: 50,
    }
}

pub fn justice_league<'a>() -> Group<'a> {
    Group {
        name: "Justice League",
        members: vec![
            superman(),
        ],
    }
}
```

Finally, we can implement the last test case:

```rust
#[test]
fn should_return_largest_group() {
    let groups = vec![samples::sinister_six(), samples::justice_league()];
    let result = a::find_largest_group(&groups);
    assert!(result.is_some());
    assert_eq!(result, groups.last());
}
```

The final step is to restrict the visibility of the test sample's functions. So far, we have used the `pub` modifier to make functions accessible from anywhere. Yet, Rust allows us to restrict the visibility of a function to a specific module:

```rust
pub(in crate::tests) fn batman<'a>() -> Super<'a> {
    Super {
        super_name: "Batman",
        real_name: "Bruce Wayne",
        power: 50,
    }
}
```

## Conclusion

Among all benefits of Rust, I did enjoy the most the user-friendliness of the compiler error messages. With just my passing understanding, I was able to fix most of the errors quickly.

I might not have gotten everything right at this time. If you're an already practicing Rustacean, I'll be happy to read your comments.

The complete source code for this post can be found on GitHub:

{% github ajavageek/start-rust %}

**To go further:**

* [Rust by Example](https://doc.rust-lang.org/stable/rust-by-example/)
* [The Cargo Book](https://doc.rust-lang.org/cargo/)
* [Syntax cheat sheet](https://cheats.rs/)

_Originally published at [A Java Geek](https://blog.frankel.ch/start-rust/1/) on May 30<sup>th</sup>, 2021_