Rust-02: The beginning

nidomiro

Niclas

Posted on November 7, 2022

Rust-02: The beginning

At first, I didn't like Rust.
Managing memory myself is something I don't want to do.
After a first look at Rust and setting it aside for a few weeks, it appeared back on my radar.
I thought: "This time I'm looking deeper into it".
So I started by reading the Rust Book; and I'm still reading it and find new concepts that are really clever.

One of the first things I discovered is that I do not need to manage memory myself.
The majority of the management will be done by the Rust compiler; I just have to follow some Rules.
These Rules, called "Ownership", are a bit confusing to start with, but make more sense, the more you think about them, and the more you use them.
In combination with the strict differentiation between mutable and immutable structures, you can write code that is easy to read, easy to think about and resource efficient - a combination, I thought never possible.

It was about chapter 5 when I started my example project - a calculator.
I will use the project in this series to explain some things and concepts.
Keep in mind that this project is just a learning project.
It makes no claim to be perfect, nor very Rust-idiomatic; but it will improve over time and with understanding more concepts.

You can find the repository here: https://github.com/nidomiro/learning-rust-calculator

I skip the setup process of cargo as it is super simple and well described in the Rust-Book.

My first action was to implement a simple addition.
You can enter 5+5 and the program outputs 10 - hopefully ;).

fn main() {
    println!("Please enter a calculation");

    let mut input = String::new();

    std::io::stdin()
        .read_line(&mut input)
        .expect("Could not read line");

    let parts: Vec<i32> = input.split(['+'])
        .map(|x| x.trim().parse().expect("Only numbers are allowed"))
        .collect();

    if parts.len() != 2 {
        panic!("Currently only + is a supported operator")
    }


    println!("Parts: {:?}", &parts);

    println!("{} + {} = {}", parts[0], parts[1], parts.iter().sum::<i32>())
}
Enter fullscreen mode Exit fullscreen mode

Source: https://github.com/nidomiro/learning-rust-calculator/blob/e96ce3c449972be7854545bf0c5d9c386eccbd42/src/main.rs

Let's examine what happens here.
First we print a text to the console to let the user know what we want from him.
Then we declare a mutable String instance.
We use this mutable String and pass a mutable reference to the read_line method.
When the user enters text and presses Return, the entered text will now be stored in the variable input.

So far so good, but hat does this line .expect("Could not read line") do?
To understand why this line is there, we have to dive a bit deeper into Rust's error-management.
In general there are two types of errors: recoverable errors and unrecoverable errors.
If you encounter an unrecoverable error, it is not safe to continue executing the program and therefore the program will just exit.
This is called panic.

The other type of errors are recoverable errors.
A recoverable error is represented by the Result enum.
This enum has two possible values, Ok if there is no error and Err if a recoverable error occurred.
This is one of the concepts I learned over the years, and I really like it.
You can see at the method signature what errors can occur and don't need an ugly try-catch block to handle errors (performance is also better).
But what does the line .expect("Could not read line") now do?
read_line returns a Result, and therefore the compiler forces us to handle the error-cases.
.expect("Could not read line") is a shortcut for "I don't care if errors occur. Just crash the program (panic) with the given message.".
It is obvious that you need to be careful when to use this.
In this case it is ok, because the program is just for learning purposes and not a production program.

You can read more about error handling in Chapter 09 "Error Handling" of the Rust-Book.

With that out of the way we can continue with the input-processing.
First we split the String at the character '+' and then trim the resulting parts (remove excess whitespace characters) followed by an integer-conversion (.parse()).
The collect() is needed, because map is evaluated lazily and therefore doesn't return a vector itself.

After checking if we got two numbers in the vector and panicking if not, we print the calculation and its result.

And there it is, we have our first functioning Rust program :)

Over the next posts, we will improve the code and add functionality.

💖 💪 🙅 🚩
nidomiro
Niclas

Posted on November 7, 2022

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related