Rust from a Gopher - Lessons 5 & 6

Hello and welcome to the fourth 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.

This post is another double lesson so without further ado, I’ll jump right in!

Chapter five launches off by introducing you to the concept of storing custom data types in Rust. Naturally Rust uses structs for this, and here we began our lesson.

Defining structs

The first difference I noted when comparing Rust structs to Go’s is that struct definitions don’t require a type keyword. We use this in Go because otherwise you’d be instantiating an anonymous struct. Is there such a thing in Rust? It may not actually matter; the only times I’ve ever really used Go anonymous structs was for unmarshalling nasty YAML, and it was just a cleaner way to code a dirty job. Ugh, I get stomach pains just remembering… Generally, unmarshalling JSON/YAML in Go is a bad experience. I’m eager to see how Rust does it better considering it has access to generics and Options.

Moving along I found “Field Init Shorthand” looked nice, as does the “Struct update syntax”. Both Rust and Go are about 10 years old as of writing this - I wonder how many of these quality of life features were in earlier versions of Rust? It worries me thinking Go misses out on neat sugar like this simply because of how strict they have been with backward compatibility. (Or are there other reasons…?)

After regular structs and tuples are explained, the book introduces “Tuple structs”. At first this made me think we were going overboard on convenience features, but the Color and Point examples were very good. I don’t think there’s a way to alias tuples in Go like this - but actually, that doesn’t seem be what Rust is doing either. You can define methods for tuple structs! Though as far as I know the only way to access the internal fields in these methods is via self.idx. I’d wager if you’re going to that much trouble with a Tuple struct then it probably just makes sense to use a fully named struct.

A Walk with String’s

The struct examples in this chapter are entirely littered with uses of String::from("someusername123") to instantiate fields. This looks pretty cumbersome and seems like we’d be better of just using &str’s. So I went ahead and tried to convert the example myself…

Oh no… He tried to do something in Rust

… whereupon the compiler swiftly kicked me in the head, quite helpfully dislodging a row of aching teeth I’ve had pain me for some 9 years. lifecycle… or something along those lines was what it muttered to me, as did the book so helpfully but only a few lines later. I suppose that will teach me for prying; thankfully my toothache’s gone.


How to code with structs

After the basic explanations of structs, the chapter introduces some example of using structs in your program to make it clearer/better. Nice, I just wish I had brought some cheese and crackers for this.

Okay joking aside, the last part of the example given is pretty nifty. It tries to get you to print Struct info with the println macro. The book even goes so far as to tell about the error messages regarding the std::fmt traits. So far, indeed, that I’m starting to hazard a guess about what exactly traits are… *ahem*

It sounds like traits are simply interface definitions that get applied/inferred to/from your structs via duck typing. i.e. exactly like an exported interface in Go. I really want to click through to the trait definitions in std::fmt to validate this, but instead I’ll let that nugget of burning excitement simmer for another 5 chapters.

Looking back to the struct examples, we’re also introduced to the first use of annotations in code. Me being me, I got delivered this beauty from my first attempt to use the derive annotation:

error: cannot find attribute `derivce` in this scope
 --> src/main.rs:6:7
  |
6 |     #[derivce(Debug)]
  |       ^^^^^^^ help: a built-in attribute with a similar name exists: `derive`

I really like how the compiler is being extra helpful here. It’s definitely going a couple extra miles more than some other languages (cough Java cough)…

Beside thinking derive looks quite cool, I’m quietly tempering myself with caution. Something I find incredibly useful in Go is being able to use +%v (code example) in Printf calls to dump struct field-names and values. Am I correct in sensing that by default there is no way to do this in Rust?


Is a single Impl really enough for you?

We’ve put all the things we can do with an instance of a type in one impl block rather than making future users of our code search for capabilities of Rectangle in various places in the library we provide.

In Go you can define methods for your structs anywhere in a package and often it does make sense to split those methods across multiple files. I never saw this is an issue because between IDEs and package docs you will always have the entire method list of a struct. I suppose browsing raw source files on Github is a counter-example… I’d rather that not be the case to optimize, but hey maybe it’s just a styling thing.

Please don’t make me ->

Automatic referencing and dereferencing - good, I like this in Go and would be sad to not have it. I’d be curious to hear if Rust had any teething issues though. For example, I was surprised to see Go still had bugfixes for this feature being released as late as 2020!

6. Enums and Pattern Matching

Yay Enums!!! Speaking of Enums, I just want to say that Enums of Enum is a cool sounding band name. Maybe also Sisters of the Enum too. What about Gimme all dat Enum? Ok, I’ll stop.

Rust lets you write methods for your enums. I had a hinkling that enums were going to be good in Rust and this is surely an invigorating start.

What next electrified me was learning there’s no null in rust. Minfdul of keeping my head level I grew skeptical of this claim as the mild shock quickly dissipated. A helpful commenter from a previous learn-rust post informed me of the existence of a never type; there exists a unit struct; Options have None… So hey… there most certainly feels like you’re not missing anything. Also just saying - the Tony Hoare quote really seemed a bit dramatic, I’ll share it below:

I call it my billion-dollar mistake. It was the invention of the null reference in 1965. At that time, I was designing the first comprehensive type system for references in an object oriented language (ALGOL W). My goal was to ensure that all use of references should be absolutely safe, with checking performed automatically by the compiler. But I couldn’t resist the temptation to put in a null reference, simply because it was so easy to implement. This has led to innumerable errors, vulnerabilities, and system crashes, which have probably caused a billion dollars of pain and damage in the last forty years.

Doesn’t this do an injustice to all of the language designers that came after Hoare? Maybe I’m being overly sensitive or taking it out of context. Maybe the shackle of a single empty reference type really did chain us to a billion dollars of damage. What’s a billion these days anyway?

Option <T>

Option<T> included in the prelude - oh boy tell me more about this prelude! Seriously, this might actually be the most titillating line of the book yet. I sincerely hope we get more about it later, otherwise it is in my too-dig list.

If we use None rather than Some, we need to tell Rust what type of Option we have, because the compiler can’t infer the type that the Some variant will hold by looking only at a None value.

This makes perfect sense, and I think it covers some of my questions (which were also answered in the comments) in my lesson-2 post.

Overall, the Option<T> pattern looks great, but it also kind of seems like if err != nil in Go - just enforced at the compiler level rather than by coding/convention. To be clear here; this most certainly is a huge benefit, an excellent guard to have; but it also sounds like three lines of boilerplate every time I have uncertainty in a codepath… So it could grow old, fast. But I’ll still appreciate it at least

As I’m publishing this post I have just finished reading chapter 9 and found the ? operator which can be used for both Result<T, E> and Option<T>… Forgive me for I had sinned; I am unworthy of the craggly crabnapples dangling off the Rust diety. Doubt, no more I shall. I am bound by the claw of CraggleCrok.


Mild Meandering

I wonder if there is a way to reproduce what the if let example is doing but without the matching. How verbose would that be?

i.e. How would you logically do this without if let or match?

fn main() {
    let some_u8_value = Some(0u8);
    if let Some(3) = some_u8_value {
        println!("three");
    }
}

… is there some typeof builtin or a reflection package we would use?

Backs away, slowly

Conclusions

These chapers were solid. I think the core of program structure and data representation/manipulation has been covered. What I really want to learn more about right now is lifetimes, as this seems critical to being able to something. Now don’t ask me what that something is yet but it seems like it’s a pretty cool something.

Clearly the only way for me to find out is to keep reading - so until next time Gophers & Rustaceans - farewell!

comments powered by Disqus