Learning To Use Rust Over a 16-Hour Hackathon

And loving every moment of it

Sid Shankar
Prodigy Engineering
9 min readJan 21, 2020

--

Torque! So much Torque! — Photo by Matt Hoffman on Unsplash

We had a year-end hackathon here at Prodigy Education. Over the last two days of 2019, teams and individuals got together to bring their ideas to life.

For fun.

To learn something new.

Or maybe to prove out a concept or idea.

I had been passively consuming information and code snippets about Rust for the past few weeks and thought the hackathon was the perfect opportunity to get hands-on with the language.

The time-constrained nature of the hackathon forced me to move and learn quickly, while also solving a real-world problem.

Why Rust?

Getting a chance to peek under the hood again — Photo by Tim Mossholder on Unsplash

I’ve spent eight of my first 10 years as a software professional using C and C++.

On a positive note, I’ve loved the clarity a language like C++ can offer in terms of static typing and the compiler catching errors early.

My personal opinion of C++ is that it:

  • Easily allows engineers to shoot themselves in the foot
  • As a language, has become quite bloated and complex, in my opinion
  • Lacked good/standard/widely adopted package management

Since my move to building web applications, I’ve worked exclusively with Python and JavaScript, using frameworks like Django, Flask, and Express.

My experience so far with modern web application development stacks in Python and JavaScript is they offer good application iteration and delivery speed but can be CPU and memory intensive, sometimes even when idle.

I do often find myself missing the type safety, speed, and leanness of a (well-written) compiled C++ application.

I wanted to explore what web-application development might look like with a lean, bare-metal programming language like Rust.

No runtimes. No garbage collection. Just a binary loaded and run by the kernel.

The Goal

The goal was to build an application with a Rust back end and a JavaScript + React front end to display the contents of an S3 bucket as an image gallery. Users would be able to:

  • Browse all the images in the bucket (pagination optional)
  • Upload images
  • Add tags to the image during upload
  • Search and filter by file name or tags

All fun hackathon projects need a name, so I decided to call this one:

RustIC -> Rust + Image Contents

Let’s hack something great — Photo by Clark Tibbs on Unsplash

I’d consider the hackathon a personal success if I walked away with (in descending order of priority):

  • A basic understanding of Rust, its type system, and memory model
  • Exploring S3 functionality with respect to presigned links for files and arbitrary tags
  • A functioning proof-of-concept application

Since my emphasis was on delivering functionality while balancing that with learning, a lot of the code I wrote necessarily leaned toward being functional — i.e., code organization and efficiency were secondary concerns.

Principles of Rust

Before I began, I was curious to learn the principles the language designers had in mind when creating this new language. I found an abridged (albeit dated) version and a more detailed one.

Contrary to what I read on a lot of blogs, it is possible to have memory leaks (through improperly setup reference cycles) and perform unsafe operations (in an unsafe block) in Rust. As mentioned in the detailed Rust FAQ,

“We [the language creators] do not intend [for Rust] to be 100% static, 100% safe, 100% reflective.”

Dazzling, intricate, sophisticated — like Rust — Photo by Matt Richardson on Unsplash

Getting Started on the Back End

A Google search for “Rust web framework” yielded a top hit for Rocket. I dove into the site to find the documentation and examples seem well laid out and thorough.

One caveat with Rocket is it needs the nightly version of Rust to operate. Not a big deal in th context of a hackathon.

The GitHub repo has plenty of examples. Perfect.

I used Cargo to set up a new project, added Rocket as a dependency in the TOML file, and following the instructions in the Getting Started guide for Rocket, set up a route:

#[get("/")]
fn index() -> &'static str {
"Hello, world!"
}
fn main() {
rocket::ignite().mount("/", routes![index]).launch();
}

For anyone familiar with Django, Flask, Express etc., this should look very readable. As a Rocket user, you use macros as decorators on functions to map a route to a handler function.

At compile time, the macros are expanded. This will be completely transparent to you. If you’re curious to look at the expanded code, the cargo-expand crate can be leveraged.

Here are the interesting/challenging highlights of my app-building experience with Rust:

Specifying the route response

I wanted my contents route to list all the files in the S3 bucket by returning a JSON data structure.

Notice (in the code below) how you can look at the function handler associated with the route to immediately determine the response shape.

Crafting the response shape was extremely easy. If you think of the JSON data as a structure with fields, where each of those fields could, in turn, contain its own structure and fields, then this maps well to a Rust struct.

So all you have to do is to define the struct(s) that constitute the response data shape and tag each one with:

#[derive(Serialize)]

struct(s) tagged with #[derive(Serialize)] can be passed to the JSON function from rocket_contrib::json::Json, and away we go!

Working with multipart uploads

I was knee-deep in building the application with Rocket when I realized I’d likely have a form on the front end that’d result in a POST with multipart/form-data.

Unfortunately, Rocket (as of v0.4) doesn’t have multipart support. This seems to be scheduled for v0.5.

This meant having to work with the multipart crate and needing to figure out integration with Rocket. The resulting code worked but could’ve been a lot cleaner with in-built multipart support in Rocket.

Configuring CORS

Once the routes were in place and I’d done some testing using curl and Postman, it was time to integrate with the front end. I needed to set the response headers appropriately to avoid CORS issues.

Again, there’s no in-built support in Rocket.

Looking through the issue log on the GitHub repo, I saw some of the building blocks for a solution, which resulted in this:

Shortly afterward, I discovered rocket_cors, which significantly slimmed things down.

Up and running

A simple cargo run invocation in the terminal spits out this beautiful output:

The Activity Monitor on my machine is telling me this app is humming along, taking just 2.7MB of memory.

And that’s the unoptimized debug version.

The same app built with the — release flag sips 1.6MB of memory.

With the Rust-based back end service up, hitting a /contents route on the service produces:

On the front end, the work was relatively simple. Using:

The user sees a page where they can browse images and search/filter by file name or tags.

The user can drag and drop to upload files and has the ability to tag files before they’re submitted for upload.

What I Loved About Building an App With Rust

  • Cargo, especially for dependency and application management, is amazing
  • The compiler is extremely helpful for compiler errors. Check out this blog post where the author uses the errors from the compiler to guide him toward the right solution. My experience definitely mirrored this.
  • I was pleasantly surprised by the availability of crates for every piece of functionality I needed.
    ○ Web frameworks? Check.
    ○ Middleware options for the web framework? Check.
    ○ S3 and other AWS service client libraries? A Redis client library? JSON serialization and deserialization? Check, check, and check!
Crates galore on crates.io! — Photo by Tim Mossholder on Unsplash
  • The online Rust Playground, where I could experiment with small code snippets
  • The Rust language server, which integrated nicely into Visual Studio Code, and is meant to be front-end agnostic, providing live error checking, formatting, symbol lookup, etc. I was able to experiment and make progress for hours without building.

Inconveniences, Surprises, and Troubles

Although Rust documentation itself is fantastic, the documentation for some of the crates I had to depend on left a lot to be desired — particularly around usage examples.

Some crates had well written integration tests, which offered clues on usage. As always, Stack Overflow and Reddit helped.

“Where’s the documentation?” — Photo by Ben White on Unsplash

In addition:

  • Understanding ownership, lifetimes, and borrowing can produce a steep learning curve initially, especially when pushing to deliver functionality over a two-day hackathon. With a little poking, I could draw parallels with C++ and figure things out, but I still found myself scratching my head at times.
  • Of all the things, Strings tripped me up for a few minutes. String vs. &str was confusing — until I took the time to understand ownership, lifetimes, and borrowing. Then, it all makes sense.

Other Observations About the Language

  • There’s no real null type in safe Rust (although there’s a notion of a null raw pointer). What you’ll typically see is the liberal use of Option types. which may evaluate to Some or None, with pattern matching frequently coming into play.
  • Pattern matching is awesome. It’s one of my favorite features in Scala, and I love that Rust has it too. The code looks expressive and allows the compiler to flag unhandled cases.
  • Speaking of safe and unsafe, you can still do lower-level programming, for say, interfacing with languages like C using unsafe blocks of code. While a lot of correctness checks don’t go away, you’re allowed to do things like dereference raw pointers within unsafe blocks. Seeing an unsafe block is informative for anyone reading the code, too.
  • Allocation on the heap is done via Box, as opposed to using new and delete. This feels strange at first but is easy to get used to. There are also other smart pointer options in the standard library if you need to do, say, reference counting or you need to hold onto weak references.
  • Exceptions are interesting in Rust, in that it doesn’t have any. Your options are either a Result<T, E> for recoverable errors or using the panic! macro to signal an unrecoverable error.

The guide states:

“In many cases, Rust requires you to acknowledge the possibility of an error and take some action before your code will compile. This requirement makes your program more robust by ensuring that you’ll discover errors and handle them appropriately before you’ve deployed your code to production!”

Key Takeaways and Lessons

  • John Carmack once described the feeling of writing Rust code as “very wholesome.” I’d agree with that sentiment. This hackathon felt a lot more like opening doors to constantly discover and learn new things, rather than an indiscriminate code-cobbling effort.
  • In hindsight, I should’ve chosen my web framework a little more carefully. With a little more thought, I may have gone a different route (no pun intended). Web framework options are discussed and compared here. I may consider iron, actix-web, or even tiny-http for my next project.
  • I’ve only scratched the surface as far as truly learning Rust goes — a 16-hour hackathon does not make a Rustacean, although my efforts here have got me curious to dig a lot deeper into the language. I’m excited for the future of Rust. I think it brings a lot of discipline to application building, is a very powerful and expressive language, and already offers runtime speed and memory performance on par with C++.

Thank you for reading, I hope you learned something new about Rust or Rocket. Happy hacking!

The Engineering team at Prodigy believes in using the right tool for the right job. We actively use Javascript, Python and Ruby, and are always willing to experiment with new technologies if we see a compelling use case.

The views and opinions expressed in this post are those of the author and don’t necessarily reflect the view of Prodigy Education or the engineering organization within Prodigy Education.

This post was originally published on January 10th, 2020 on the Better Programming publication.

--

--

Sid Shankar
Prodigy Engineering

Senior Engineering Manager @ GitHub ~ opinions are my own and do not represent the views of my employer.