Rust from a Gopher - Lessons 3 & 4

Update: There has been discussion on this post on Hacker News - feel free to see the comments there

Warning: Incoming opinion monologue; feel free to skip to The Lesson Review review if that’s what you’re after

Hello and welcome to the third post in my series about learning Rust. In case you want to hit it from the start, here’s a link to the first one! 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.

How Rust is Makin' Me Feel

Let me begin not by thwacking loosely about in concepts I know little yet of, as there’s plenty of time for that, but instead allow me to paint the landscapes I see for both Rust and Go. At quite the visceral it occurred to me that these modern languages are built not just to make our coding lives more delightful - but instead to shepherd us into particular engineering and organizational goals.

The needs for new general purpose languages no longer stem from the “simple” software problems. Take the problems of using variables (Assembly), using custom data types (C), cross-platform, cross-architecture compatibility (JVM languages) or even being dead simple (Python). Today’s languages can instead choose features from all the preceding and then apply engineering and organizational direction.

The Coercion of Go

I’ve been writing in Go as a hobby since 2014 but professionally only since 2018. As a hobbyist I’ll admit I used to think “Go is opinionated”. That seemed cool, because opinions mean something! Right? But why was it opinionated Levi, WHY? WAKE UP MAN!

Well today I have a completely different look on it. Go is simply a language that is very safe to share across engineers. This is because engineers don’t need to make a lot of decisions when they use Go. If an application has had its design completed in theory then in Go it often really is just a matter of whacking out the code to make it a reality.

Just look at how much of Go’s development tooling Google owns. With Java, I remember choosing between Ant or Maven for your build tooling. Go doesn’t let you chose. The closest we got to having a choice was with dep for dependency management. But finally Google caved and gomod was brought about as the standard. Go does its best to take choices away from you. You don’t need to chose between Tomcat or Jetty - the Go net/http package will handle 10kRPS for you no problem. Hell I’ve looked at apps serving 50kRPS, whilst logging a third of said requests simply using fmt.Println. You just don’t need to stray far from the standard library to scale and that is a huge plus of the language. (A large part of this is also comes from the fact that webservers haven’t changed a heck of a lot in the past 10 years so Go didn’t have to “keep up” - on the other hand my experience with http2 in Go has been far from ideal).

Anyone can pick up your code and fix it

This is by far the biggest selling point of using Go in a company. If you need to hire help you can find basically anyone with backend experience, and they will pick up 90% of Go in a week or two. You don’t need to worry at all if they have experience with Struts, JUnit or Spring - what’s in the standard library is plenty. I mean it. Do they need to know about passing pointers or values? Not really - general software engineering practices like peer review and simple unit testing will uncover those types of issues with ease.

Now on the other hand - who in their right mind would hire me to join their Rust team right now? Nobody - because I would be a gigantic liability to that team.

Go’s purpose is for dev shops to crank out web services that are scalable, easy to develop and do not require mission critical performance.

And this is fine. I still love Go.

Rust no Rust

I’m a noob. 100%. But even so, in my feeble mind I can already see what Rust is. I see a very sharp knife; but this knife is completely and utterly shrouded and encased in tamper-proof, child-proof, thief-proof hardened and sealed plastic shells. Yes shells as in the plural of shell. These shells are even adult-proof too, where the adult is a generic engineer trained generally in other languages only. The compiler is the packaging, and it will let you wield the knife when it knows exactly what your action plan is. -But oh no, not just any plan will do, your plan must adhere to each and every rule and regulation from the Knife Safety Measurement Act of 1938 and its associated amendments!! (This may not be strictly true as I’ve heard about an unsafe keyword).

But why so much plastic broseidon? You know, and I know that it’s to keep mild-minded people like myself exactly, from nicking fingers with that very sharp knife. Warping back to a meta-level, those fingers don’t even necessarily belong to me the coder, but to the end users of the code. It is no secret to anybody even slightly interested in Rust that a major driving factor for the language was to be able to replace C++ code with something as efficient but much less susceptible to security exploits. Thus, the safety plastic aims not to protect individual coders, but the coding organization.

Okay, you’ve probably heard enough of my opinion, let’s move on before this analogy implodes and actually hurts someone.

Give Me The Lessons!

3. Common Programming Concepts

One of the first ‘huh’ moments in this lesson was this compiler message:

compiler error:
For more information about this error, try `rustc --explain E0384`.

Naturally I ran the command listed, which took me to a less window (buffer?) containing the following:

An immutable variable was reassigned.
Erroneous code example:
'''
fn main() {
    let x = 3;
    x = 5; // error, reassignment of immutable variable
}
'''
By default, variables in Rust are immutable. To fix this error, add the keyword
`mut` after the keyword `let` when declaring the variable. For example:
'''
fn main() {
    let mut x = 3;
    x = 5;
}
'''

…and this slightly let me down. There isn’t a whole lot of information in this “explanation”. I proceeded to allow rustc to “explain” some more random error codes to me, most of them seemed also to be quite small or to have been deprecated. I am hoping either A) I don’t have to use this feature much or B) I can make rustc/cargo/intellij just tell me the detailed stuff by default.


const MAX_POINTS: u32 = 100_000; // wtf is this ugly numeric shit

For some reason this irked me when I first saw it (the comment taken verbatim from my lesson notes). On second look it actually seems really, really helpful for readability.


Shadowing

This seems like a really handy trick. You get to write code as if your variable was immutable, but the compiler does the switching for you. What is really messing my head up though is that you learn about Shadowing before you learn about Ownership. So does my naive understanding of shadowing change after this? I don’t think so … at least. Here’s something I wrote to verify my learnings:

fn main() {
    let mut s = String::from("hello");
    let r1 = &mut s;
    let r1 = &mut s;
    println!("{}", r1);
}

…and it works. It also answers a question I had when originally learning ownership. For some reason when reading the examples in the book I came away thinking Rust could infer when immutable references' scopes end, but not mutable ones. I was puzzled by this and had a follow-up item but this little example proves I was wrong. Happy days!


Small Nit?

The Invalid array element access example didn’t work - it was supposed to produce a runtime error but instead it failed to compile:

   Compiling variables v0.1.0 (/home/levi/rustprojs/variables)
error: this operation will panic at runtime
 --> src/main.rs:5:19
  |
5 |     let element = a[index];
  |                   ^^^^^^^^ index out of bounds: the len is 5 but the index is 10
  |
  = note: `#[deny(unconditional_panic)]` on by default

I don’t even know if I should call this a nit or just straight up be impressed. Did Rust evolve to the point the book can no longer trick me into making a runtime panic?! This is some straight-jacket level packaging I swear to god. Much applause.


A smol walk in the woods

Having picked up many a good pointer during this lesson I figured I had bumped myself up a couple of notches. Maybe white-belt, double-yellow-tip or something along those lines… “Let’s go for a wander” I thought to myself with quiet confidence. Looking left, and then looking right, under the shelter of a single raised eye-brow I chose to descend toward the belly of Rust.

Ctrl + *click*

I chose a simple avenue. I chose something concrete to all beginners. I chose the pinnacle of Hello_World

I chose to dive into println!

… and dive I did. Straight into the ground after clanking my head into a hard iron post of this macro. My eyes but glimpsed Sauron directly and from then on and always, I am blind:

#[macro_export]
#[stable(feature = "rust1", since = "1.0.0")]
#[allow_internal_unstable(print_internals, format_args_nl)]
macro_rules! println {
    () => ($crate::print!("\n"));
    ($($arg:tt)*) => ({
        $crate::io::_print($crate::format_args_nl!($($arg)*));
    })
}

What in God’s sweet name is this acrid assault on all of my senses?

I am going to be honest here. There is no way in hell I will understand this macro by the end of my twentieth lesson. Will I? I’m not sure actually. I guess there’s hope? Chapters 10, 14 and 19 all look they will be mandatory. Hoping Intensifies … ?


Expressions versus Statements

If you add a semicolon to the end of an expression, you turn it into a statement, which will then not return a value. Keep this in mind as you explore function return values and expressions next.

This was a mind fuck - about 10 minutes before reading this I thought to myself that semi-colons seemed optional and kind of pointless in rust. Boy was I well outside the woods.

A later thought did have me wondering though; do Rustaceans really just write expressions at the end of their getter functions or is it more common to explicitly return?


Comments Nit

In Rust, the idiomatic comment style starts a comment with two slashes, and the comment continues until the end of the line. For comments that extend beyond a single line, you’ll need to include // on each line, like this:

// blah blah

// bloh bloh

What’s weird is that /* */ style comments are clearly supported - I tried it after reading this. The entire Comments sub-lesson doesn’t make any mention of them though? I’m guessing this has something to do with the idiomatic qualifier the authors used. If this is the case then that’s fine, I’d just like to know who defines the idioms and better yet, point me to the Rust Idioms Bible. In Go we have effective Go.

I guess it occurs to me now that this very book could be the Bible I’m faffling about…. But even then I still think it should mention /**/’s and why we shouldn’t used them…


4. Understanding Ownership

One thing you see mentioned a lot about Rust when you’re new is the ownership checker, so I was very happy to finally reach a chapter showing me something really novel. By the way, if you’re still with me then congratulations - you have the patience of a bullfrog.

Now I’ll start this chapter off by saying I was tricked about strings.

The book really led me to believe there are literally two types of strings in Rust, which I personally thought was a very ballsy move on Rust’s part. Only over the next thirty minutes did the authors mercifully reveal to me that there is but ONE String! The “special” string literals are really just some sugar over an immutable string slice reference. Ultimately I kind of felt duped by this but I guess if people aren’t familiar with slices and references etc then maybe this teaching approach makes sense.

Ultimately though, in a kind of anti-climactic bust, this chapter was actually quite uneventful for my rusty brain. (Hey; I’ve been living with terrible Go puns since forever ok!) There were only two things from the chapter in which I wanted to know more about. The first is Traits, oh my goodness these sound intriguing - I’ll be patient for this though. The second was from this tidbit in the introduction:

With the String type, in order to support a mutable, growable piece of text, we need to allocate an amount of memory on the heap, unknown at compile time, to hold the contents. This means:

  • The memory must be requested from the memory allocator at runtime.
  • We need a way of returning this memory to the allocator when we’re done with our String.

That first part is done by us: when we call String::from, its implementation requests the memory it needs. This is pretty much universal in programming languages.

…I just wish the book told me a bit more, i.e. does String make a libc malloc call or does it just make a new slice and the comiler has its own behind the scenes memory layer? I’m 90% sure this will need to become clearer later in order to write good Rust code, which is in stark contrast with Go where have little-to-no control over heap vs stack allocations (and subsequently it doesn’t pay off very well to know or rely on how the runtime allocator works as this has always been subject to change too).

This is definitely something I could just dive into String to check out for myself but for the sake of this blog series and my own time each night, I’m largely restricting my explorations to what the book is teaching me verbatim for the minute. Also remember what Sauron did to my eyes with println!

Conclusion

To cap-off a double-chapter post here, I think the lessons were solid. The scariness of immutable variables by default is subsiding as I learn some of the patterns for dealing with them. Taking a step back it is nice to have a language introduce the concepts of the stack and heap at such an early stage, I re-read the C book about a year ago and I don’t even think those concepts were mentioned in it!? In fact the last programming book I read that went deep on these concepts was the fantastic RISC-V Assembly Language book. So yeah, Rust is clearly setting the table well in advance for young crabbies.

I’m really, really looking forward to unpacking the next lessons from the Rust book and hope that you’re enjoying the journey with me. As usual I greatly appreciate any guidance and corrections in the comments, until next time Gophers and Rustaceans, Nofo a (Byebye)!

comments powered by Disqus