The COVID19 lockdown has been a pretty challenging time for everyone. Staying sane without the social interactions we normally have can be difficult. I took the time to learn a new programming language. This served several purposes.
It helped focus my mind, and keep me relatively sane, and brought my wife and daughter moments of peace. :)
- I collect programming languages. This is something I blame on university where every new concept was accompanied by a new language to introduce it.
- I believe that understanding multiple programming languages and paradigms makes me a better programmer.
Additionally, there are other considerations in learning Rust:
Working in a world full of kubernetes, I’ve come to appreciate the small download size of compiled languages like Rust, Go and C/C++ over what feels like the bloat of JIT’d languages, and scripted languages that all need to carry around an immense amount of machinery just to provide a runtime for apps written with these languages.
Startup times: Compiled languages have it hands down here. Equivalent programs in a compiled language compared to a JIT’d language are significantly faster at delivering the first byte. Again, in a kubernetes world, this can have significant advantage.
Thinking ethically, the fewer bytes we transfer and the less work we need to do at runtime, the less energy we use. This is a win for the planet and a win for your pocket. Leaner applications using less memory, and taking fewer CPU cycles, mean you need fewer resources to run those apps. For a single app, that might not mean a lot, but if you are running tens or hundreds of them, that can make a big difference to the impact on your wallet.
So how long did my journey take? I spent about two months dabbling with a toy project, plus any number of attempts to read the The Rust Programming Language. The process is ongoing though. I’m no where near entirely proficient with the language, albeit that I feel significantly more confident than I did at the start of this exercise.
My toy project; Uploader - An application to watch a directory and move the files to cloud storage. When I first thought this might be a good project with applicability to services that we write at FINBOURNE, I immediately thought of Go. In searching around though, I couldn’t find a good “file watching library” that would also watch subdirectories. So I changed tack; What if I used this to help me learn Rust? After all, I learn best while doing; the exploratory process helping to frame the unknowns of the problem domain. Lots of pre-reading doesn’t stick unless I’m also doing. Little did I realize that this project would turn out quite well as a learning exercise. It managed to hit on the following points of interest:
- trait usage
- fake objects for testing, based on a trait
- web server
- signal handling
- errors, and error handling
Now to be fair, many of those touch points would have been covered with a contrived learning application, but I was surprised at the breadth it managed to cover. And it also manages to be a useful program, with real world application. Though it may never see service as I intended it, it has served its purpose in helping me learn Rust. I could have written it in C# in a couple of hours; but I’d have missed out on this golden learning opportunity.
Here are the challenges I found to be most prominent while on my journey:
Like most languages, learning the ecosystem is challenging. It’s not that I didn’t know how to do what I wanted to do, it’s that I didn’t have any experience with the libraries that would help me achieve that goal. Learning to use libraries that have a slightly different bent than those I have been used to (many years as a C# dev) takes time. They also incorporate all the additional type metadata that Rust requires; ownership rules. Not to mention that in many cases, there are multiple libraries to choose from. Which one is the best? For what definition of best? It can drive a dev to distraction sometimes. So that’s challenge number one.
Trusting the compiler
cargo are seriously good at telling you how to fix issues they identify. In C# or Go land, I’d have seen an error and dove into the code to try and figure out what was wrong, make a change, compile, wash and repeat until it’s right.
cargo build gives you a monstrous amount of help. I spent probably a month not able to break the old habit. I couldn’t help but dive into the code and start tinkering. This led me down rabbit holes of code change to try and satisfy an error that, when I finally rolled back 10’s or 100’s of lines of code because I frankly just didn’t know what I was doing, was a very simple change, and the compiler told me what to do. It’s a different way of working, and a shining light in Rust development.
Just learning how to read the Rust documentation for libraries is a challenge and skill unto itself. To be fair, I’m probably hampered by years of barely having to look at library docs to figure stuff out, because the IDE support for code completions is quite exceptional in Visual Studio. Add in Resharper, and you barely need to go to the internet for docs. Couple that with years of understanding the ecosystem, and I could always make educated guesses as to what methods I needed to call.
It’s a simple concept. It is also difficult to do away with 25+ years of programming habit. Not having to think about ownership of data is the mainstay for all other programming languages. Suddenly add it into the mix, and it takes a little while to get used to. Does the function you are calling take ownership of the data? Do you need to use it after that function call? Copy/Clone!! Not using it, pass it in and forget about it. Borrowing? Just add
Seems simple enough, but until it becomes muscle memory in your programming, it can create a lot of issues. Not to mention the whole lifetime thing that means you sometimes just have to create a local variable that you then pass in to your function. This bogged me down any number of times, and was the main cause of my divergent coding frenzies.
They were the main challenges, but there were others. Getting a good IDE setup took time. That is until a colleague told me about Rust Analyzer. Friends don’t let friends code Rust without Rust Analyzer. Thank you to the maintainers, it’s a fabulous piece of kit.
What makes Rust special?
Where do I start? So many things to mention. I started writing a mind map, thinking it would be nice and simple. But eventually a little spider turned into a web. I’ll introduce you to the things I think are the stand outs. At least from my limited experience.
Documentation: more specifically “code as documentation”. Put a code sample in a function doc, and it too gets compiled. If the code in the doc diverges from the actual code: Compiler Error. What a win. Why don’t all languages do this?
- Error handling: Rusts error handling metaphor is to use a return type of
. There are no exceptions to be caught, just what is basically an
type. You have either, a result,
Ok(T)or you have an error
Err(E).Add the question mark operator for automatically unwrapping `Result`s and you have a pretty powerful and thorough error handling solution. Then there’s
panicbut no exception handling. You can’t throw, and there’s no
panicreally does do what it says.
- Options / lack of nulls: these two concepts go hand in hand, because without one, you could not have the other. An
Option<T>either has some data;
Some(T);or no data;
None.This results in not even a hint of the concept of a NULL.
- Ownership: I can’t believe I made it this far down the list without mentioning the ownership model. A piece of data in Rust, has one and only one owner; special multi owner types not withstanding. You can borrow that object/value, but of course, you must give it back. When borrowing, you can do either, but not both of: take one mutable reference or take infinity many immutable references (known as aliasing). It’s an XOR of mutability and aliasing.
- Lifetimes: Honestly, I can’t describe them and do them justice, just follow the link. Hand in hand with the ownership model is the concept of variable lifetimes. This can get pretty hairy at times, especially if you are doing things in a not so good way, but the compiler is pretty good at figuring out lifetimes for you, so for simple programs, it’s pretty much a no-op.
- Mutability: Rust, unlike most other languages, is immutable by default. Objects/values can still be mutable, you just have to be explicit about that choice.
That’s the headline stuff, but there’s also memory allocation/deallocation, stack allocation by default, the macro system, and smart pointers. These are just the things that I managed to come across when writing Uploader. The language is so full of interesting features you can quickly get lost. But I’d recommend you persist. Conquering a Rust program can feel pretty rewarding when you get that nice green, unencumbered build success.
Of course there’s also the build tools,
rustfmt, and the fabulous Rust Analyzer. Using these tools made building Uploader quite a joy, for all the hand wringing I did over things I didn’t understand at the time. In fact, with that combo, every time I managed to get a flawless build, I felt pretty confident that I had a working product; to my surprise, every time.
I had Rust on my to-learn list for so long, that when a viable use case for it became available, along with a viable amount of time, I jumped at it. Lockdown has saved me roughly three and a half hours of travel time per day. That’s a lot of extra sleep, a lot of extra family time, and a lot of reclaimed time to learn something new.
I haven’t finished with Rust yet. I’ve got a lot to learn still, and I genuinely found working with the language to be enjoyable, and refreshing. I plan to start a little coding club at work, and find small, non-critical use cases for Rust that will aid the learning for all those involved. Who knows, maybe one of them will be used one day in a real live production system. Wish me luck.
This article was first published on HackerNoon.