Marc's Blog

About Me

My name is Marc Brooker. I've been writing code, reading code, and living vicariously through computers for as long as I can remember. I like to build things that work. I also dabble in brewing, cooking and skiing.

I'm currently an engineer at Amazon Web Services (AWS) in Seattle, where I lead engineering on AWS Lambda and our other serverless products. Before that, I worked on EC2 and EBS.
All opinions are my own.

Links

My Publications and Videos
@MarcJBrooker on Twitter

Two Years With Rust

I like it. I hope it's going to be big.

It's been just over two years since I started learning Rust. Since then, I've used it heavily at my day job, including work in the Firecracker code base, and a number of other projects. Rust is a great fit for the systems-level work I've been doing over the last few years: often performance- and density-sensitive, always security-sensitive. I find the type system, object life cycle, and threading model both well-suited to this kind of work and fairly intuitive. Like most people, I still fight with the compiler from time-to-time, but we mostly get on now.

Rust has also mostly replaced Go as my go-to language for writing small performance-sensitive programs, like the numerical simulators I use a lot. Go replaced C in that role for me, and joined R and Python as my day-to-day go-to tools. I've found that I still spend more time writing a Rust program than I do Go, and more than C (except where C is held back by a lack of sane data structures and string handling). I've also found that programs seem more likely to work on their first run, but haven't made any effort to quantify that.

Over my career, I've done for-pay work in C, C++, Java, Python, Ruby, Go, Rust, Scheme, Basic, Perl, Bash, TLA+, Delphi, Matlab, ARM and x86 assembly, and R (probably forgetting a few). There's likely some of my code in each of those languages still running somewhere. I've also learned a bunch of other languages, because it's something I enjoy doing. Recently, for example, I've been loving playing with Frink. I don't tend to be highly opinionated about languages.

However, in some cases I steer colleagues and teams away from particular choices. C and C++, for example, seem to be difficult and expensive to use in a way that avoids dangerous memory-safety bugs, and users need to be willing to invest deeply in their code if these bugs matter to them. It's possible to write great safe C, but the path there requires a challenging blend of tools and humility. Rust isn't a panacea, but is a really nice alternative where they were fairly thin before. I find myself recommending and choosing it more and more often for small command-line programs, high-performance services, and system-level code.

Why I like Rust There are a lot of good programming languages in the world. There are even multiple that fit Rust's broad description, and place in the ecosystem. This is a very good place, with real problems to solve. I'm not convinced that Rust is necessarily technically superior to its nearest neighbors, but there are some things it seems to do particularly well.

I like how friendly and helpful the compiler's error messages are. The free book and standard library documentation are all very good. The type system is nice to work with. The built-in tooling (rustup, cargo and friends) are easy and powerful. A standard formatting tool goes a long way to keeping code-bases tidy and bikesheds unpainted. Static linking and cross-compiling are built-in. The smattering of functional idioms seem to add a good amount of power and expressiveness. Features that actively lead to obtuse code (like macros) are discouraged. Out-of-the-box performance is pretty great. Fearless Concurrency actually delivers.

There's a lot more, too.

What might make Rust unsuccessful? There are also some things I don't particularly like about Rust. Some of those are short-term. Learning how to write async networking code in Rust during the year or so before async and await were stabilized was a frustrating mess of inconsistent documentation and broken APIs. The compiler isn't as smart about optimizations like loop unrolling and autovectorization as C compilers tend to be (even where it does a great job eliding the safety checks, and other Rust-specific overhead). Some parts of the specification, like aliasing rules and the exact definitions of atomic memory orderings, are still a little fuzzier than I would like. Static analysis tooling has a way to go. Allocating aligned memory is tricky, especially if you still want to use some of the standard data structures. And so on.

In each of these cases, and more like them, the situation seems to have improved every time I look at it in detail. The community seems to be making great progress. async and await were particularly big wins.

The biggest long-term issue in my mind is unsafe. Rust makes what seems like a very reasonable decision to allow sections of code to be marked as unsafe, which allows one to color outside the lines of the memory and life cycle guarantees. As the name implies unsafe code tends to be unsafe. The big problem with unsafe code isn't that the code inside the block is unsafe, it's that it can break the safety properties of safe code in subtle and non-obvious ways. Even safe code that's thousands of lines away. This kind of action-at-a-distance can make it difficult to reason about the properties of any code-base that contains unsafe code. For low-level systems code, that's probably all of them.

This isn't a surprise to the community. The Rust community is very realistic about the costs and benefits of unsafe. Sometimes that debate goes too far (as Steve Klabnik has written about), but mostly the debate and culture seems healthy to me as a relative outsider.

The problem is that this spooky behavior of unsafe tends not to be obvious to new Rust programmers. The mental model I've seen nearly everybody start with, including myself, is that unsafe blocks can break things inside them and so care needs to be paid to writing that code well. Unfortunately, that's not sufficient.

Better static and dynamic analysis tooling could help here, as well as some better help from the compiler, and alternatives to some uses of unsafe. I suspect that the long-term success of Rust as a systems language is going to depend on how well the community and tools handle unsafe. A lot of the value of Rust lies in its safety, and it's still too easy to break that safety without knowing it.

Another long-term risk is the size of the language. It's been over 10 years since I last worked with C++ every day, and I'm nowhere near being a competent C++ programmer anymore. Part of that is because C++ has evolved, which is a very good thing. Part of it is because C++ is huge. From a decade away, it seems hard to be a competent part-time C++ programmer: you need to be fully immersed, or you'll never fit the whole thing in your head. Rust could go that way too, and it would be a pity.