Save me Ferris, save me - Part 2


Save me Ferris, save me is a blog series about working through the Rust Book as a frontend dev from non-traditional background - you can find Part 1 here

the painting "Young Woman on Her Death Bed" - it depicts a young woman looking extremely worse for wear. I have added Ferris the Rust mascot, a cute spikey orange crab beside her.

Chapter 2, titled Programming A Guessing Game, really had me thinking about instructional design for a moment: there's nothing I love more than getting that sweet dopamine hit of making a thing appear when I want it to appear.

The guessing game is a super simple terminal app that generates an integer from 1-100 in the background, asks for your guess and tells you whether it's too high or too low on a loop until you get it right.

A terminal where the command 'cargo run' has been input for a program called guessing_game.exe. The first 3 guesses are 14, 7, and 2, all of which output the result "Too big!". The final guess 1, was the smallest number possible and when guessed output "You win!"

A really rare fun number to have gotten as the hidden number for this example

There's a lot going on in this chapter and in traditional tutorial fashion it's often just enough to get the job done, but like the last chapter I'm finding I do not mind it as much as I have in the past with other learning materials. If anything it's also making me curious & excited to actually get the full picture later and/or wanting to pause and jump to some documentation now.

I'll also note again that the steps taken feel very natural and workflow-y: the layering of the logic, handling edges cases, pulling in a external library at the correct juncture - it feels like the day-to-day work from a kinder world.

In order to list all the things that jumped out at me, more or less in order, here is the final code from the chapter.

use std::cmp::Ordering;
use std::io;

use rand::Rng;

fn main() {
    println!("Guess the number!");

    let secret_number = rand::thread_rng().gen_range(1..=100);

    loop {
        println!("Please input your guess."); 

        let mut guess = String::new();

        io::stdin()
            .read_line(&mut guess)
            .expect("Failed to read line");

        let guess: u32 = match guess.trim().parse() {
            Ok(num) => num,
            Err(_) => continue,
        };
        println!("You guessed: {guess}");

        match guess.cmp(&secret_number) {
            Ordering::Less => println!("Too small!"),
            Ordering::Greater => println!("Too big!"),
            Ordering::Equal => {
                println!("You win!");
                break;
            }
        }
    }
}

std::io and std:cmp: std in Rust is quite literally the standard library, a group of things Rust imports into every program. This, at first read, definitely hit the spot in my brain where I store residual trauma from tree-shaking Node packages, but after taking a quick glance at the official documentation this went away.

For the prelude, as these standard things are called, the list is not only small (20 or less depending on your version of Rust), but small for very opinionated reasons. The documentation states:

Rust comes with a variety of things in its standard library. However, if you had to manually import every single thing that you used, it would be very verbose. But importing a lot of things that a program never uses isn’t good either. A balance needs to be struck.

std:io for this is simple input output (but does have a lot more under the hood) and std:cmp supplies some very nice built in comparison enum.

rand::Rng : the name of this bad boy had me thinking about video game rng, but its purpose besides generating us our random number and teaching more syntax was to provide a very natural and simple excuse to add an additional Rust package, called a crate.

learning to love strong types(?) : String is my first real encounter with a strong typed Type and it's methods/ associated functions. Usability-wise it's not all that different from JS's built in Object and Array methods, but I think I am starting to get an idea of where the decisions for typing converge.

I will admit that it was at this point that I stopped and did a search with the main answer being quite literally the definition of dynamic v static typing, that is that it's a runtime v compile time difference. I am liking getting the chance to prevent surprise crashes well ahead of building/running.

I'm guessing that Rust will provide a lot more methods/ associated functions or a solid amount that are more powerful/ nifty to make up for the trade off of these types being immutable. That being said I can't see why there wouldn't be some way to copy &/or extend types as needed within certain safety bounds.

trim() and parse() were the main ones here; the former, which removes new lines/carriage returns, is nifty, and the second, which straight up converts something to another type (in this case a u32 integer) seems pretty powerful.

matching : match is a "control flow construct" that takes an expression, type and all, and compares it to the "arms". This in my mind is a stronger and cooler switch statement. When we pass the secret number to .cmp and call .cmp on our guess it returns one of the 3 variants of an enum named Ordering. This alone doesn't handle things like invalid inputs, but having the bounds of finite arms with an expression that doesn't get converted to a Boolean in the background is very cool.

Result, a new bestie? : To take care of that input validation another match got added to the guess variable itself and instead of arms that correspond to the variants of Ordering we instead are introduced to the Result type (which is also an enum). It does feel like an Error object, but again quite literally more strict, not only as an enum but within matching.

From what I remember of past study I'm pretty sure Result gets used a ton (and most likely with matching) which I suspect is because it is more solid than a Boolean.


This chapter did leave me with quite a few questions that I didn't go into because they will be answered later, so good curiosity strikes again, namely like what's up with enums, what's the difference between all the things you can call (methods/ associated functions / macros), etc. I'm definitely making a list to pick back up when they do get introduced later.

Before I wrap up there is a larger meta observation I want to bring together that will probably be a major running theme throughout this entire series, something I'm calling JavaScript style swerving. Simply put it's all the ways we learn to cowboy around the fact that JS is dynamically + weakly typed and is very lenient with mutability. Now I'm not here to give my takes on the arguments around those things, but I am wondering how experiencing types (beyond TypeScript basics) and immutability is going to change my perspective overall in regards to programming/ language selection in general.

Even though I haven't made it too much far ahead of this series in terms of progress, I already know that a ton of Rust features and complexity are about to rise up to fill the time and effort I normally would have spent pulling off those swerves.

Lastly, I'm very much aware that this article (and hell the intro too) has been overwhelmingly positive. For now, even with my limited exposure to the back-end/ systems, it's because so many things hit as highly intuitive. Trim trims, parse parses, match matches - you get the idea.

But - I am wondering at what point I will begin to feel overwhelmed; I haven't even gotten to the standard data types run down yet, and if I am remembering correctly, there are literally like 3 different and dense chapters for all of those.

Oh well, we'll see :)


Sign in to leave a note.