Rust from a Gopher - Lesson 2

Hello and welcome to my second lesson in my series about learning Rust. In case you missed it, here’s a link to the first post. This entire series covers my journey from being a completely land-locked Gopher to becoming (hopefully) a hardened Rustacean, able to skitter the hazardous seabed of any application safely.

My Seabed

I’ll start this post off with a couple of notes about my setup, as truth be told I haven’t really planned much for this series and it might do well to share my context. Here is a list of the main “features” of my working environment:

  • Ubuntu 18.04 LTS
  • IntelliJ IDEA w/ Rust + IdeaVim plugins
  • Rust/Cargo 1.47.0
  • 12 core/24 thread Ryzen 3900X + 64GB ram
  • A 6 and a 27 month pair of smol humans
Goals

I’m not 100% on my goals for learning Rust, but I like the idea of writing some audio processing tooling and potentially being able to compile it to WASM. It will be a long road to get to that point though.

The Lesson

I was skeptical going into this lesson - Programming a Guessing Game really gave me flashbacks to COMPSCI101 style college courses. I actually tutored such a course in college and it left me feeling really jaded about force-feeding students logic regimes. I’ll leave that rant for another day, suffice to say - this rust lesson left me feeling quite the opposite!

Now in case you haven’t had the pleasure of running through this exercise yourself, let me just summarize it a little for you. You’re basically walked through the process of setting up a small CLI app and ultimately program (via copy-paste) a simple number guessing game. What I liked is that you’re introduced to some pretty nifty looking features of Rust. Most of the time the lesson tells you “We’ll cover this for real in chapter 1932”, but if you’re anything like me then that just isn’t quite satisfactory. The titillation has already begun. You begin to think about how those language features could be working and the very notion that underneath this hood lies a beast so ancient, so divine, so shrouded in mystique. Well my friend, that is what gets the juices flowing.

Expect, Result

The first molten ball of fiery joy to rain upon my enfeebled mind (did I mention having 2 under 2?) was this pleasant method of error handling.

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

Maybe not so much handling of an error, but leaving yourself a nice post-it note for when eventually this tiny world of your program comes to a panicked and electrifying end. But hey - it’s still pretty cool. In Go the expect line would be at least three lines long:

    reader := bufio.NewReader(os.Stdin)
    text, err := reader.ReadString('\n')
    if err != nil {
        panic("Failed to read line")
    }

Of course it’s the good old error-handling where things blow up in Go. At work I don’t notice it so much, either I’m used to it now or I don’t write a lot of business logic. The was a period last year where I wrote a CLI tool though, the error handling did erode my patience a tiny bit more then. But anyone who has written Go before knows.

Switching from an expect call to a match expression is how you generally move from crashing on an error to handling the error

Noice, I will take this! Look at my comparison to Go’s panic above, leaving random .expect’s around seems like something you probably don’t actually want to do that much, but in general it looks a lot less verbose than Go and also seems like you may have a lot more options with custom Result types too. Speaking of which…

Enums, Generics

pub enum Result<T, E> {
    Ok(T),
    Err(E),
}

WOW, I didn’t know I’d be so happy to see generics and enums. Maybe it’s the Java in me that’s gleeful or maybe these constructs can be super bloody useful! To make it better, these Rust bad boys don’t look like just any low-grade iota style enums, they look spicy! Not only can you combine generics with your enums them but it looks like you can actually have any types you want linked to the values.

Other Bits

“think of {} as little crab pincers that hold a value in place”

Really guys?>? Maybe I can start calling them claws. Ya’know - like Gopher claws? Come to think of it… They look more like chicken feet. Maybe that’s it. Chicken feet. Ooh darnit - I had 1 too many chicken feet in my loop!


“Rust doesn’t yet include random number functionality in its standard library. However, the Rust team does provide a rand crate."

At first I was shocked by this line. I use rand quite often in Go and have become used to just having it. Only after I looked into the crate thought did I begin to calm down. This lib is managed by the Rust team. I guess with crates.io being the standard platform for Rust libraries, they can afford to detach more things from the standard libs. Maybe I’m wrong though. Maybe the decision was much more organic than that.

In any case the actual package is pretty swell looking. More akin to scipy.stats this crate actually lets you generate randomness from non-uniform distributions. I’m still too noob understand but I think you may be able to fairly easily integrate the noise into your own datatypes too. I hope I can answer this question for myself soon.

One last part about the crate not being in the standard lib. I hope dependency management isn’t too arduous a task in Rust, I’ve gotten very used to having basically everything I need from the stdlib or my $GOPATH, and having my IDE just import things on demand.


“The number [cargo] 0.5.5 is actually shorthand for ^0.5.5, which means “any version that has a public API compatible with version 0.5.5.”

This sounds neat - I wonder how said public API is defined and moreover how (or if) cargo enforces the semver to match it.


cargo doc --open

Having pretty docs locally is great but I need to figure out how to get them in IntelliJ. Right now the source code is still ugly to my eyes and the helper hover-text is just as gnarly. For now I’ve just set this command as a build target for when I need it but I’m sure it will eventually annoy me enough to find a better answer.


match

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

So this is interesting. I’m trying to consider what its parallel would be in Go. The obvious answer would be switch, but the fact that you can use it to assign values (like below) makes it feel more like a selector from Puppet. This looks like a very very sweet keyword.

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

Reading and understanding this part of the example filled me with genuinely glee. A giddy lightness in the stomach often not experienced since the brighter days of ones youth. Being able to assign the value in the sane-case to guess whilst gracefully handling errors in a single block is plain great. To make things even more giddy, the authors then dropped this bomb on me..

Additionally, the u32 annotation in this example program and the comparison with secret_number means that Rust will infer that secret_number should be a u32 as well

I absolutely love type inference in Go. I := all day every day, but when you tell me the compiler will do that by default and then refine it even further based on use-cases of your variable? There is just too much good stuff happening here. Seriously I can’t even remember the last time I felt so delighted to program.

Why doesn’t this work?

Okay so I was getting super hyped up about the match train and figured that other than error handling, you could probably put some logic around your sane-case too - I mean this is the assignment of our immutable variable - where the heck else would you put it? So I tried this:

    let guess: u32 = match guess.trim().parse() {
        Ok(num) => num + 1,
        Err(_) => continue,
    };

Which greeted me with:

Error: 
22 |             Ok(num) => num + 1,
   |                ^^^ cannot infer type
   |

^ right hand side must be u32 though?

I tried a few strengthless punches to make it compile but ultimately I was too weak. The compiler beat me and even though the hints look quite obvious - I am but a mere noob. The good news is that I’m in no rush and I am sure the benevolant authors of the Rust book will teach me. For I am weak and unworthy but they shall make me crusty!!!!!!

Conclusions

This chapter actually ended up being far larger than I anticipated. It exposed me to concepts that touched me deeply and for that I am enamored and ready for more. Stay tuned for Chapter 3 and feel free to school me on my speculations and open questions.


Update: Find the next post in the series here

comments powered by Disqus