Damola's blog

Testing the waters - Thoughts on Rust.

The not-so-steep uphill climb to building in Rust.

December 17, 2020 | 10 min. read

Rust holds the crown for ā€œmost-lovedā€ language (5 years in a row) according to the stack overflow survey - quite an impressive feat. I actually didnā€™t know Rust was this beloved until I started writing this post and checked its position. I however have been wanting to foray into development with WebAssembly (wasm) for some time. Rust probably has the largest ecosystem for building with WebAssembly. And best suited for wasm over languages like Java or C# (or even GoLang) since they all have garbage collectors and require at least some form of minimal run time to be packaged with the code to run. Compiled rust wasm will generally come out smaller than the alternatives since it has no garbage collector - and smaller is always better with web apps.

Rust is like C++, but it makes memory management/safety much less painful. The hard work is done at compile time. Just thinking of the many times I got segfault writing C++ in collegeā€¦ yikes šŸ˜Ÿ. This is where Rust shines - while staying as efficient as C++. I say ā€œefficientā€, without any numbers but Google ā€œRustā€ and the first sentence should be ā€œPerformance.ā€ (Literally, that one-word sentence).

I have tried Rust before, but the first try left me veryā€¦ confused. I was ā€œfighting the borrow checkerā€ (a key way Rust guarantees memory safety) so I sort ofā€¦ gave up put it in the backlog. Knowing that I wanted to do something (anything) with Rust, my first goal was finding out ax ā€œHello Worldā€ program. It was mostly by chance, but I found yew on the ā€œrealworldā€ repo. Yew is a framework for building web apps with Rust. I will not write about setting up Rust, the rust documentation is a great resource (and always my go-to when lost).

Yew was fairly straightforward to get started with. Thereā€™s a sample app in the documentation that I was able to run within minutes. The framework is component-based so coming from mostly React, I felt at home - at least from a high level. Components have state and receive props from their parents. I highly recommend reading the yew docs if curious. The documentation is solid. (A very common theme in Rust-land).

I wanted create something that does a little more than showing hard-coded text, maybe call an API and update the UI based on the response. So, I decided to create a clone of rickandmortyapi.com with Yew. I donā€™t think one should reach for Yew just to build a web app that calls an api and writes the response. (You could get away with no framework and just write a few lines JavaScript). But this was a happy medium for me, especially since I didnā€™t have a computationally complex problem to solve. I used create yew app to bootstrap a new app and created an (ugly) clone of the site.

The result is here. Hereā€™s a snippet of a component method (rendered) thatā€™s called at every render (a useEffect in React). Here, Iā€™m calling the api after the first render to fetch characters for the first page:

fn rendered(&mut self, first_render: bool) { if first_render { let link = self.link.clone(); wasm_bindgen_futures::spawn_local(async move { let cs = get_page(1).await; match cs { Err(e) => { ConsoleService::error("failed to get characters"); ConsoleService::error(&e.to_string()); } Ok(r) => { link.send_message(Msg::UpdateCharacters(r.results)); link.send_message(Msg::UpdateCurrentPage(1)); link.send_message(Msg::UpdatePageInfo(r.info)); } }; }) } }

One major highlight from this is pattern matching. The code would not compile if the error branch was not handled. Seemingly trivial but, I think itā€™s one of those guard rails thatā€™s really protecting the programmer from self-inflicted pain.

The get_page function is from rick_and_morty crate which was added after finishing the yew app and realizing it should be easy to refactor and publish the logic as a separate library (I noticed Rust was missing from the helper libraries available here).

I am so surprised at how easy it was for me to create a library in Rust. Really, why isnā€™t every other language like this? This started with me wanting to learn Rust by writing a WebAssembly app; It ended with me publishing a crate in Rust (and absolutely loving the entire process).


Editor Support:

I was hoping to find a dedicated IDE for Rust. It seems like it would benefit a lot from having a full-fledged environment (like Goland) - there isnā€™t one. But, there is a pretty solid extension for VS Code. I was not expecting anything impressive. VSCode is, a code editor at the end of the day. And it especially seems to struggle with everything thatā€™s not JavaScript or TypeScript. My main issue is how it slows down with larger codebase, ā€œGo To Definitionā€ sometimes refusing to work. The Rust extension was pretty good. I think an IDE would do even better (and Iā€™m hoping JetBrains makes one šŸ¤žšŸ¾). The main issues I ran into were (rare) occasions where hovering a function, Struct, method etc would not show the proper documentation for it, and trying to go to the definition would also not work. Also, lint and error checking always take an extra second to show after saving. So it always feels like the editor is trying to catch up to me. After linting and compiling is done, and the messages show up in the editorā€” wow, I donā€™t think Iā€™ve worked with a compiler as helpful as Rustā€™s.

Compiler Messages

Rustā€™s compiler errors and warnings are probably the most pleasant part of working in Rust when just getting started. I donā€™t remember the compiler being this friendly the first time I tried Rust (3-ish years ago). It points out exactly where somethingā€™s gone wrong and, often, even suggests how to fix it. This is an example of one such message that shows in VSCode:

A very helpful message from the Rust compiler.
A very helpful message from the Rust compiler.

This makes coding a lot easier - and the image is possibly the most trivial example. There are also a lot of compiler flags to warn/error on things like unused imports/dead code, and even fail on missing documentation! This may not be groundbreaking but I think Rust`s approach just makes it so ā€¦ approachable :).

Life Time And Borrow Checking.

I canā€™t write too much on this as I am definitely not an expert. But it is important to write just about any Rust code. Itā€™s how Rust makes sure references that are no longer used are discarded (and discarded references are never used). I didnā€™t find it too hard to wrap my head around this - even though it was my point of failure the first time I tried Rust. Itā€™s like learning how scopes work and then turning it up to 200. I recommend reading the Rust bookā€™s primer

Pattern Matching.

After working with Rustā€™s Option and Result types. Going back to writing JavaScript feels like a regression. Rust has a match expression which is like a switch statement (or if/else ) in most languages - except it makes you write code better. A lot better. Things like switch fall-through - which, you know, why? WHY do so many languages have switch statements fall through by default?? (and require using a break - leading to weird bugs in code, because a switch branch is missing a break). No more dealing with all that nonsense. match to the rescue! What I like the most about match is the exhaustiveness of it. If you switch over an enumeration of 3 possible values. And then later the enum is extended to 4. In most languages, the previous code would still work (but could potentially have bugs because of a missing case). With match - that missing case is a compile error! These are the kind of edge cases youā€™d need unit tests for ā€” and now theyā€™re free. This really shines when dealing with an Option. A wrapper for something that could have a Some(value) or be None. match forces you to write the code to handle both cases. Every time. So youā€™ll never accidentally pass a None to a place that canā€™t handle it! (No more null pointer exceptions) The same for Result (as in the sample code). Ensuring an error is always handled. Thereā€™s some helper methods for working with these types. But the general rule is: you wonā€™t unintentionally miss an edge case. This is great. It actually frees up more mental real state knowing the compiler has your back that way.

Macros

Macros in Rust were sort of like voodoo the first time I saw it (why is it println!() and not println()). I mostly ignored how it works and just used it when I needed it. I started to appreciate the power of macros when I jumped into yew and noticed that they had created a jsx-like syntax for writing declarative html views. Thereā€™s an html! macro that renders html views. In the rickandmorty app, this is a sample macro that renders a single character:

html! { <li class="character-list-item"> <div class="character-container"> <div> <img src={copy.image} alt={"image of"} /> </div> <div> <p>{copy.name}</p> <p>{"Status: "} {copy.status}</p> <p>{"Location:"} {copy.origin.name}</p> </div> </div> </li> }

I havenā€™t written any macros (and I doubt Iā€™ll need to anytime soon), but my little trick of understanding them is theyā€™re a way of auto-generating code (ā€œmeta-programmingā€). So the macro is ā€œexpandedā€ at compile time into rust code (i.e. code that creates the dom elements in the case of html!). If youā€™re wondering why macros, why not functions? Good question; Rust doesnā€™t have function overloading, or variadic parameters, so in this html case where the dom tree structure (the function input) cannot be known beforehand, the equivalent function is basically impossible to write. Hence a macro is needed.

Package Management (Cargo, crates.io, docs.rs)

Dealing with webpack has to be one of the last things I ever want to do when working in a large JavaScript project. Itā€™s like it was deliberately designed to be complex (complicated?) and intimidating. I mean, I get that itā€™s actually solving a problem but, itā€™s a pain. CLI tools like vue, angular and create-react-app have made things a lot easier but - those are all framework-specific. So whenever thereā€™s a need to do something the defaults arenā€™t designed for - well, that becomes a rabbit hole. Cargo is what I expect a package manager to look like. There are commands for everything from creating a new rust library or binary, running tests, generating docs, building the code, packaging and publishing.

Knowing the process is so straightforward (and very well documented) motivated me to create a new library (cargo new --lib {lib name}) and publish it on (cargo publish) crates.io and have the documentation automatically generated on docs.rs (with a viewable local version through cargo docs). I did across about 3 days in my spare time. From scratch, learning the language, ecosystem and publishing a package. Thatā€™s not something I think is easily done in many other languages. The actual library (and what it does), I donā€™t think is impressive. But how the entire process worked - makes me feel like I can more easily build something in Rust, and not feel like Iā€™m fighting everything else along the way.

Rust is a relatively new language compared to most other popular languages like Python, JavaScript, C/C++; even GoLang is older (and Go probably excels to some extent on the same things mentioned). So it has had the chance to learn from all the bad decisions (and does JavaScript have a lot of those) - and really do things right. It excels at It Just Worksā„¢ . And for that, I am glad. I canā€™t wait to figure out what to do with all this power.

To reiterate: Iā€™ve only spent a few days with Rust, there is a ton more stuff that Rust offers that I havenā€™t tried yet. To the Rust expert, forgive my naivetĆ©.

My wishlist for Rust long-term:

  • A solid cross-platform sdk for building native apps. I generally prefer the speed of native apps versus electron-based apps. But there really arenā€™t a lot of great alternatives to electron and JavaScript here (ie. VSCodeā€™s unimpressive handling of large files vs Sublime). I think Rust would be a perfect fit.
  • Native DOM apis for wasm. Right now, wasm code, regardless of the language, still depends on the JS dom api for making changes to UI. So technically, theyā€™re not yet any faster than JavaScript. That will (hopefully) eventually change. And when it does; I think the web will become an even more platform for buildingā€¦ everything.


Adedamola Shomoye

By Adedamola Shomoye

Join the...