Systems Languages: An Experience Report

What Is a Systems Language?

Let’s back up a bit. What is a systems language? Well, I think that depends on where you are in the stack, and who you ask. In general, I would suggest the definition of a systems language is a language that can be used to implement the components your systems runs atop.

Systems for the rest of us

What runs on the server side is an incredibly complex composed set of systems. Applications, and business logic is often written in languages like Javascript, Python, Ruby, Elixir, or Java along with an opinionated toolkit like Express, Flask, Sinatra, Phoenix, or Dropwizard. These frameworks rarely run in isolation, and often depend on a variety of backend systems, like databases, data pipelines, load balancers, and caches.

What’s holding up this mountain of shit?

At the end of the day, nearly all of these systems rely on our operating systems userland and the Linux Kernel. Most of the distributions of Linux still use a userland largely written in C. Although, GNU C has a ridiculous amount of extensions, enabling everything from a formalized memory model to thread-local variables, it is still C. There have been attempts to introduce userlands in Go, Rust, and Nim, but none of these projects have gained wide adoption.

Escaping C

I wanted to see if I could write a very simple userland utility that I would normally write in C in another language. Go has replaced much of my usage of C these days, but it’s not a language I’m happy with, but rather it is the pragmatic choice. I get memory management, a ton of build tooling, and a much simpler language. I lose generics, macros, and a whole lot of insight into what I’m doing.

The usecase

I tried to write a program for a very simple task for a thing at work. It was a POSIX signal helper. Effectively, it was the entrypoint into a container, that would be responsible for calling a shutdown script during a container termination. It would run services that were meant to run to termination, so these services are meant to only terminate if, either due to a failure caused by programmer error, or machine fault, or due to an external signal.

A Signal Wrapper

Evaluation

I tried to write this a bunch of languages. In most cases, it was a false start, and the language fell short of my needs in capabilities. To spoil things early, I ended up compromising, and writing this in Go. Even as close to C as Go is, it is still awkward. Since fork/exec is managed via os/exec, you can’t simply start to listen to all signals received in the main goroutine, without breaking exec.

Nim

Nim was a false start. I needed to run the code in a separate process group than the signal-wrapper so when the process group gets the signal. You can do this “by hand” if you go through the fork/exec process by hand, and call setpgid.

Pony

Pony is still in its infancy. I am not really using it for its original use case. I was bound to hit missing features.

Reason (OCaml)

I tried to write this in Reason, Facebook’s flavour of OCaml. It’s awesome to see a functional programming language on this list. As of writing this, Reason is primarily aimed at frontend developers, and not “native” developers, as they call us.

Rust

I was also able to complete the task in Rust. I really wanted to like Rust. Rust feels like all of the complexity and difficulty of C++, without much added benefit for simple programs.

Everything Still Sucks

If you’ve gotten this far, you’ll realize that everything is still terrible. If I want to implement anything at this layer of the system, my choices are largely still C, and Go. I’m excited because a number of new participants have entered the ring. I’m unsure that I’m ever going to want to use Rust, unless they have a massive attitude adjustment. I’m excited to see Nim, and Pony mature.

--

--

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store