Writing a single-file website with Rust

· 6 min read

At the company I work for, there is something called a “professional education leave”, which lets me take a day off to learn something new or attend conferences or whatever professional education means. I took the opportunity to learn the Rust programming language.

I didn’t want to follow tutorials, so I thought of a project to learn with, which ended up being “make a single-file website web server”. It was inspired by the article my website is one binary by j3s.

Rust first impressions

fn main() {
  println!("Hello World!");
}

My first impression was that it is a mature programming language that is crazy about compile-time stuff. For example, the above println! call is a macro, which expands to something more verbose… Apparently, with macros you can also make full-blown DSLs that are directly embedded in Rust code.

The rustc compiler’s error messages were very good.

I think I haven’t really dived deep enough to discover the standout features that I keep hearing about like memory management safety. So unfortunately I didn’t get the hype at this time.

Building a web server

The standard Rust library provides a TCP module, but no HTTP one. So it’s a level lower than NodeJS, which is understandable.

I didn’t want to implement the HTTP protocol, so I searched for an HTTP library.

I found rouille, a web micro-framework. I installed it via cargo which is like npm but for Rust.

It was really easy to get it set up and running.

For starters, I made two pages: / and /about.

// main.rs
use rouille::Response;
use rouille::router;

mod pages {
  pub mod index;
  pub mod about;
}

fn main() {
  rouille::start_server("0.0.0.0:8080", move |request| {
    let response = router!(request,
      (GET) (/) => {
        pages::index::page(request)
      },

      (GET) (/about) => {
        pages::about::page(request)
      },

      _ => Response::empty_404()
    );

    return response;
  });
}

Notice the router! macro! Apparently it allows me to write GET and the routes / and /about plainly into code. Those aren’t strings! Nor are they Rust keywords! It’s macro magic. It’s even more magical than Kotlin’s lambdas. I think this is a powerful but dangerous tool which could make learning Rust codebases harder. I like it though. Instead of parsing these routes at runtime, errors can be caught at compile time.

Rust modules

One little snag was that Rust’s module system was a little confusing, coming from ES6 where everything is explicit.

In Rust, modules map to files. Module naming and hierarchy follows the filesystem structure.

I didn’t fully understand modules properly but it seems that I can use the mod keyword to import modules. And that was enough knowledge to proceed with the project.

In the above code I separated the code that renders the pages into separate files in pages/index.rs and pages/about.rs. To import them in the main file, I had this declaration at the top:

mod pages {
  pub mod index;
  pub mod about;
}

As I defined a top-level page() function in each of these files, calling them from the main file would be something like this: pages::index::page(request).

Rendering HTML

The top-level page functions simply return static HTML Responses for now.

Here’s one that handles the root / route:

// pages/index.rs
use rouille::Request;
use rouille::Response;

pub fn page(_request: &Request) -> Response {
  return Response::html(r#"<!doctype html>
    <title>Tiny site</title>
    <h1>Hello, world!</h1>
    <p>Welcome to my tiny site!</p>
    <a href="/about">About</a>
  "#);
}

Which renders this page:

To avoid repetitively escaping double quotes in HTML, I used the raw string literal which looks r#"something like this"# to write the HTML response in Rust.

HTML templates / components

I haven’t done this, but I imagine components or “includes” can be implemented by simply calling component functions normally and concatenating the resulting HTML fragments together. Nothing fancy required, for a tiny website.

I wonder if there are templating systems that take advantage of Rust’s compile-time macros. A quick search revealed a lot of them. In fact, that seems to be the “Rust way” of doing HTML templates. One example is Maud:

html! {
  article data-index="12345" {
    h1 { "My blog" }
    tag-cloud { "pinkie pie pony cute" }
  }
}

That looks a lot like Jetpack Compose in Kotlin. Seems like fun way to write HTML. I fear I might be taken by a strange mood and convert my entire website into Rust. I guess paid hosting for non-static sites would be a good deterrent.

Conclusion

That was a nice dip into Rust. I’m still interested in the hyped-up features like memory management but I guess an HTML web server wouldn’t need much of those things after all. Maybe the next Rust learning exercise would be something like a small video game. Now that’s something that would require memory management and algorithms.

Here’s the repo for my tiny web server in Rust: github.com/Kalabasa/tala.