MoreRSS

site iconCorrcodeModify

This is an ongoing series of articles about idiomatic Rust and best practices.
Please copy the RSS to your reader, or quickly subscribe to:

Inoreader Feedly Follow Feedbin Local Reader

Rss preview of Blog of Corrcode

Be Simple

2025-09-11 08:00:00

The phone buzzes at 3 AM.

You roll out of bed, open your laptop, and see this in the logs:

thread 'tokio-runtime-worker' panicked at 'called `Result::unwrap()` on an `Err` value: 
Error("data did not match any variant of untagged enum Customer at line 1 column 15")', 
src/parsers/universal.rs:47:23

You open the codebase and find this:

pub struct UniversalParser<T: DeserializeOwned> {
    format: Box<dyn DataFormat>,
    _marker: std::marker::PhantomData<T>,
}

impl<T: DeserializeOwned> UniversalParser<T> {
    pub fn parse(&self, content: &str) -> Result<Vec<T>, Box<dyn std::error::Error>> {
        self.format.parse(content)
    }
}

A few thoughts rush through your head:

“What the hell is a PhantomData?”
“Why is there a trait object?”
“This is going to be a long night.”

The error must be buried somewhere in the interaction between that DataFormat trait, the generic parser, and serde deserialization. You scroll through 200 lines of trait implementations and generic constraints. Each layer adds another level of indirection. The stack trace is 15 levels deep. It’s like peeling an onion… it makes you cry.

You run git blame and curse the colleague who wrote this code. Whoops, it was you a few months ago.

Quick rewind. The phone buzzes at 3 AM.

You roll out of bed, open your laptop, and see this in the logs:

Error: CSV parse error at line 847: invalid UTF-8 sequence at byte index 23

You find this code:

#[derive(Debug, Deserialize)]
pub struct Customer {
    pub name: String,
    pub email: String, 
    pub phone: String,
}

pub fn parse_customers(csv_content: &str) -> Result<Vec<Customer>, csv::Error> {
    let mut reader = csv::Reader::from_reader(csv_content.as_bytes());
    reader.deserialize().collect()
}

All right, we seem to be parsing some customer data from a CSV file.

You look at line 847 of the input file and see corrupted character encoding. You remove the bad line, deploy a fix, and go back to sleep.

Don’t Be Clever

Rust programmers tend to be very clever. Too clever for their own good at times. Let me be the first to admit that I’m guilty of this myself.

We love to stretch Rust to its limits. After all, this is Rust! An empowering playground of infinite possibility. Shouldn’t we use the language to its full extent?

Nothing in Rust forces us to get fancy. You can write straightforward code in Rust just like in any other language. But in code reviews, I often see people trying to outsmart themselves and stumble over their own shoelaces. They use all the advanced features at their disposal without thinking much about maintainability.

But here’s the problem: Writing code is easy. Reading it isn’t. These advanced features are like salt: a little bit can enhance the flavor, but too much can ruin the dish. And advanced features have a tendency to overcomplicate things and make readability harder.

Software engineering is all about managing complexity, and complexity creeps in when we’re not looking. We should focus on keeping complexity down.

Of course, some complexity is truly unavoidable. That’s the inherent complexity of the task. What we should avoid, however, is the accidental complexity, which we introduce ourselves. As projects grow, accidental complexity tends to grow with it. That is the cruft we all should challenge.

And simplicity also makes systems more reliable:

Simplicity is prerequisite for reliability.

I don’t always agree with Edsger W. Dijkstra, but in this case, he was spot-on. Without simplicity, reliability is impossible (or at least hard to achieve). That’s because simple systems have fewer moving parts to reason about.

Good code is mostly boring; especially for production use. Simple is obvious. Simple is predictable. Predictable is good.

Why Simple is Hard

But if simplicity is so obviously “better,” why isn’t it the norm? Because achieving simplicity is hard! It doesn’t come naturally. Simplicity is typically not the first attempt but the last revision. 1

Simplicity and elegance are unpopular because they require hard work and discipline to achieve.

Well put, Edsger.

It takes effort to build simple systems. It takes even more effort to keep them simple. That’s because you constantly have to fight entropy. Going from simple to more complex is much easier than the reverse.

Let’s come back to our 3 AM phone call.

The first version of the code was built by an engineer who wanted to make the system “flexible and extensible.” The second was written by a developer who just solved the problem at hand and tried to parse a CSV file. Turns out there was never once a need to parse anything other than CSV files.

One lesson here is that the path to complexity is paved with good intentions. A series of individually perfectly reasonable decisions can lead to an overly complex, unmaintainable system.

More experienced developers tend to use more abstractions because they get excited about the possibilities. And I can’t blame them, really. Writing simple code is oftentimes pretty boring. It’s much more fun to test out that new feature we just learned. But after a while we forget how Rust beginners feel about our code: it’s the curse of knowledge.

Remember: abstractions are never zero cost. 2

Not all abstractions are created equal.

In fact, many are not abstractions at all — they’re just thin veneers, layers of indirection that add complexity without adding real value.

Fernando Hurtado Cardenas

Abstractions cause complexity, and complexity has a very real cost. At some point, complexity will slow you down because it causes cognitive load. And cognitive load matters a lot.

The people who are starting with Rust are often overwhelmed by the complexity of the language. Try to keep that in mind as you get more proficient with Rust. If you fail to do that, you might alienate team members who are not as experienced as you, and they might give up on the project or Rust altogether.

Furthermore, if you leave the company and leave behind a complex codebase, the team will have a hard time maintaining it and onboarding new team members. The biggest holdup is how quickly people will be able to get up to speed with Rust. Don’t make it even harder on them. From time to time, look at Rust through beginner’s eyes.

Generics Are A Liability

For some reason I feel compelled to talk about generics for a moment…

Not only do they make the code harder to understand, they can also have a real cost on compile times. Each generic gets monomorphized, i.e. a separate copy of the code is generated for each type that is used with that generic at compile time.

My advice is to only make something generic if you need to switch out the implementation right now. Resist premature generalization! (Which is related – but not identical to – premature optimization.)

“We might need it in the future” is a dangerous statement. Be careful with that assumption because it’s hard to predict the future. 3

Your beautiful abstraction might become your biggest nemesis. If you can defer the decision for just a little longer, it’s often better to do so.

Generics have an impact on the “feel” of the entire codebase. If you use a lot of generics, you will have to deal with the consequences everywhere. You will have to understand the signatures of functions and structs as well as the error messages that come with them. The hidden compilation cost of generics is hard to measure and optimize for.

Be careful with generics. They have a real cost! The thinking should be “this is generic functionality” instead of “I could make this generic.”

Let’s say you are working on a public API. A function that will be used a lot will need to take some string-based data from the user. You wonder whether you should take a &str or a String or something else as an input to your functions and why.

fn process_user_input(input: &str) {
    // do something with input
}

That’s quite simple and doesn’t allocate. But what if the caller wants to pass a String?

fn process_user_input(input: String) {
    // do something with input
}

We take ownership of the input. But hold on, what if we don’t need ownership and we want to support both?

fn process_user_input(input: impl AsRef<str>) {
    // do something with input
}

That works. But do you see how the complexity goes up?

Behind the scenes, it monomorphizes the function for each type that implements AsRef<str>.

That means that if we pass a String and a &str, we get two copies of that function. That means longer compile times and larger binaries.

The problem is so simple, so how did that complexity creep in? We’re trying to be clever. We are trying to make the function “better” by making it more generic. But is it really “better”?

All we wanted was a simple function that takes a string and does something with it.

Stay simple. Don’t overthink it!

Say we’re writing a link checker and we want to build a bunch of requests to check the links. We could use a function that returns a Vec<Result<Request>>.

pub(crate) fn create(
  uris: Vec<RawUri>,
  source: &InputSource,
  root_dir: Option<&PathBuf>,
  base: Option<&Base>,
  extractor: Option<&BasicAuthExtractor>,
) -> Vec<Result<Request>> {
    let base = base.cloned().or_else(|| Base::from_source(source));
    uris.into_iter()
        .map(|raw_uri| create_request(&raw_uri, source, root_dir, base.as_ref(), extractor))
        .collect()
}

Or, we could return an iterator instead:

pub(crate) fn create(
    uris: Vec<RawUri>,
    source: &InputSource,
    root_dir: Option<&PathBuf>,
    base: Option<&Base>,
    extractor: Option<&BasicAuthExtractor>,
) -> impl Iterator<Item = Result<Request>> {
    let base = base.cloned().or_else(|| Base::from_source(source));
    uris.into_iter().map(move |raw_uri| create_request(&raw_uri, source, root_dir, base.as_ref(), extractor))
}

The iterator doesn’t look too bad, but the vec is simpler. What to do? The caller likely needs to collect the results anyway. Since we’re processing a finite set of URLs, the link checker needs all results to report successes/failures, and the results will probably be iterated multiple times. Memory usage isn’t a big concern here since the number of URLs in a document is typically small. All else being equal, the vec is probably the simpler choice.

Simple Code Is Often Fast Code

There’s a prejudice that simple code is slow. Quite the contrary! It turns out many effective algorithms are surprisingly simple. In fact, some of the simplest algorithms we’ve discovered are also the most efficient.

Take quicksort or path tracing, for example. Both can be written down in a handful of lines and described in a few sentences.

Here’s an ad-hoc version of quicksort in Rust:

fn quicksort(mut v: Vec<usize>) -> Vec<usize> {
    if v.len() <= 1 {
        return v;
    }

    let pivot = v.remove(0);

    let (smaller, larger) = v.into_iter().partition(|x| x < &pivot);

    quicksort(smaller)
        .into_iter()
        .chain(std::iter::once(pivot))
        .chain(quicksort(larger))
        .collect()
}

The idea is pretty simple and can fit on a napkin:

  1. If the list is empty or has one element, it’s already sorted and we’re done.
  2. If not, pick a random element as the pivot. (For simplicity, we pick the first element here.)
  3. Split the list into two sublists: elements smaller than the pivot and elements larger than or equal to the pivot.
  4. Sort each sublist recursively.
  5. By combining the sorted smaller list, the pivot, and the sorted larger list, you get the fully sorted list!

The implementation is not too far off from the description of the algorithm.

Yes, my simple version only supports usize right now, but my point is that simple algorithms pack a punch. This is an O(n log n) algorithm. It’s as fast as it gets for a comparison-based sort and it’s just a few lines of code. 4

Often, simple code can be optimized by the compiler more easily and runs faster on CPUs. That’s because CPUs are optimized for basic data structures and predictable access patterns. And parallelizing work is also easier when that is the case. All of that works in our favor when our code is simple.

Somewhat counterintuitively, especially when you’re doing something complicated, you should be extra careful to keep it simple. Simplicity is a sign of deep insight, of great understanding, of clarity—and clarity has a positive effect on the way a system functions. And since complicated systems are, well, complicated, that extra clarity helps keep things under control.

What I appreciate about Rust is how it balances high-level and low-level programming. Most of the time, I write Rust code in a straightforward manner, and when that extra bit of performance becomes critical, Rust always lets me go back and optimize.

Keep Your Fellow Developers in Mind

Most of the code you’ll write for companies will be application code, not library code. That’s because most companies don’t make money writing libraries, but business logic. There’s no need to get fancy here. Application code should be straightforward.

Library code can be a slightly different story. It can get complicated if it ends up being an important building block for other code. For example, in hot code paths, avoiding allocations might make sense, at which point you might have to deal with lifetimes. This uncertainty about how code might get used by others can lead to overabstraction. Try to make the common case straightforward. The correct path should be the obvious path users take.

Say you’re building a base64 encoder. It’s safe to assume that most people will want to encode a string (probably a unicode string like a &str) and that they want to use a “canonical” or “standard” base64 encoding. Don’t expect your users to jump through hoops to do the most common thing. Unless you have a really good reason, your API should have a function like this somewhere:

/// Encode input as Base64 string
fn base64_encode(input: &str) -> String;

Yes, you could make it generic over AsRef<[u8]> or support multiple alphabets:

/// Generic base64 encoder supporting multiple alphabets
fn base64_encode<T: AsRef<[u8]>>(input: T, alphabet: Base64Alphabet) -> String;

…and you might even offer a builder pattern for maximum flexibility:

let encoded = Base64Encoder::new()
    .with_alphabet(Base64Alphabet::UrlSafe) // What is UrlSafe?
    .with_decode_allow_trailing_bits(true) // Huh?
    .with_decode_padding_mode(engine::DecodePaddingMode::RequireNone); // I don't even...
    .encode("Hello, world!");

But all that most users want is to get an encoded string:

let encoded = base64_encode("Hello, world!");

You could call the function above base64_encode_simple or base64_encode_standard to make it clear that it’s a simplified version of a more generic algorithm. It’s fine to offer additional functionality, but don’t make the easy thing hard in the process.

Simplicity is especially important when working with other developers because code is a way to communicate ideas, and you should strive to express your ideas clearly.

Tips For Fighting Complexity

Start Small

Jerry Seinfeld had two writing modes: creating mode and editing mode.

  • When in creation mode, he’s exploring possibilities and letting ideas flow freely.
  • When in editing mode, he’s refining, cutting, and polishing.

These modes require different mindsets, and trying to do both simultaneously leads to paralysis. As a consequence, Seinfeld would never edit while creating because it would kill the creative flow.

The same principle applies to coding. Don’t try to architect the perfect solution on your first attempt. Write the naive implementation first, then let your inner editor refine it. Switch off that inner critic. Who knows? You might just come up with a simpler design.

Resist the Temptation To Optimize Early

It can be tempting to use all of these fine, sharp tools you have at your disposal. But sharp tools they are! To master Rust is to say “no” to these tools more often than you say “yes.”

You might see an optimization opportunity and feel the urge to jump at it. But time and again, I see people make that optimization without prior validation. Measure twice, cut once.

Delay Refactoring

That might sound counterintuitive. After all, shouldn’t constant refactoring make our code better as we go?

The problem is that we have limited information at the time of writing our first prototype. If we refactor too early, we might end up in a worse place than where we started.

Take the CSV exporter from the beginning as an example: a smart engineer saw an opportunity to refactor the code in order to support multiple input formats. That locked us into a place where we had a generic exporter, which became a huge debugging burden while preventing us from seeing a better abstraction had we deferred the refactoring. Maybe we would have noticed that we’re always dealing with CSV data, but we could decouple data validation from data exportation. If we had seen that, it would have led to better error messages like:

Error: Customer 123 has invalid address field: invalid UTF-8 sequence at byte index 23: Address: "123 M\xE9n St."

This opportunity was lost because we jumped the gun and refactored too early.

I propose solving the problem at hand first and refactoring afterward. That’s because refactoring a simple program is way easier than doing the same for a complex one. Everyone can do the former, while I can count on one hand those who can do the latter. Preserve the opportunity to refactor your code. Refactoring might look like the smart thing to do at the time, but if you allow the simple code to just stick around for a little longer, the right opportunity for the refactor will present itself.

A good time to reflect is when your code starts to feel repetitive. That’s a sign that there’s a hidden pattern in your data. The right abstraction is trying to talk to you and reveal itself! It’s fine to do multiple attempts at an abstraction. See what feels right. If none of it does, just go back to the simple version and document your findings.

Performance Crimes Are “OK”

Rust is super fast, so you can literally make all the performance crimes you want. Clone liberally, iterate over the same data structure multiple times, use a vector if a hashmap is too daunting.

It simply doesn’t matter. Hardware is fast and cheap, so put it to work.

Be Curious But Conservative

All of the above doesn’t mean you should not learn about all of these abstractions. It’s fun to learn and to be knowledgeable.

But you can focus on learning new concepts without hurting yourself. Understanding macros, lifetimes, interior mutability, etc. is very helpful, but in everyday “normal” Rust code you almost never make use of these concepts, so don’t worry about them too much.

Use all the features you need and none that you don’t.

How to Recognize The Right Level of Abstraction

One litmus test I like to use is “Does it feel good to add new functionality?”

Good abstractions tend to “click” together. It just feels like there’s no overlap between the abstractions and no grunt work or extra conversions needed. The next step always feels obvious. Testing works without much mocking, your documentation for your structs almost writes itself. There’s no “this struct does X and Y” in your documentation. It’s either X or Y. Explaining the design to a fellow developer is straightforward. This is when you know you have a winner. Getting there is not easy. It can take many iterations. What you see in popular libraries is often the result of that process.

The right abstractions guide you to do the right thing: to find the obvious place to add new functionality, the right place to look for a bug, the right spot to make that database query.

All of that is easier if the code is simple. That’s why experienced developers always have simplicity in mind when they build out abstractions.

It’s possible that the most common error of a smart engineer is to optimize a thing that should not exist in the first place. Cross that bridge when you get there.

Write Code for Humans

Be clear, not clever. Write code for humans, not computers.

Simplicity is clarity. Simplicity is to succinctly express the essence of a thing. Simplicity is about removing the unnecessary, the irrelevant, the noise. Simple is good. Be simple.

  1. I think there’s a similarity to writing here, where elegance (which is correlated with simplicity, in my opinion) requires an iterative process of constant improvement. The editing process is what makes most writing great. In 1657, Blaise Pascal famously wrote: “I have only made this letter longer because I have not had the time to make it shorter.” I think about that a lot when I write.

  2. For reference, see “The Power of 10 Rules” by Gerard J. Holzmann of the NASA/JPL Laboratory for Reliable Software.

  3. I should know because I passed on a few very risky but lucrative investment opportunities because I lacked the ability to accurately predict the future.

  4. Of course, this is not the most efficient implementation of quicksort. It allocates a lot of intermediate vectors and has O(n^2) worst-case performance. There are optimizations for partially sorted data, better pivot selection strategies, and in-place partitioning. But they are just that: optimizations. The core idea remains the same.

Season 4 - Finale

2025-07-24 08:00:00

It’s time for another recap including our highlights of Season 4.

We’ve been at this for a while now (four seasons, and 32 episodes to be exact). We had guests from a wide range of industries: from Microsoft to Astral, and from password managers to satellite systems.

This time, it’s all about using Rust for foundational software, which is software that is critical to a team or even an entire organization. Rust is a great fit for this type of software!

KSAT

2025-07-10 08:00:00

As a kid, I was always fascinated by space tech. That fascination has only grown as I’ve learned more about the engineering challenges involved in space exploration.

In this episode, we talk to Vegard Sandengen, a Rust engineer at KSAT, a company that provides ground station services for satellites. They use Rust to manage the data flow from hundreds of satellites, ensuring that data is received, processed, and stored efficiently. This data is then made available to customers around the world, enabling them to make informed decisions based on real-time satellite data.

We dive deep into the technical challenges of building reliable, high-performance systems that operate 24/7 to capture and process satellite data. Vegard shares insights into why Rust was chosen for these mission-critical systems, how they handle the massive scale of data processing, and the unique reliability requirements when dealing with space-based infrastructure.

From ground station automation to data pipeline optimization, this conversation explores how modern systems programming languages are enabling the next generation of space technology infrastructure.

Proudly Supported by CodeCrafters

CodeCrafters helps you become proficient in Rust by building real-world, production-grade projects. Learn hands-on by creating your own shell, HTTP server, Redis, Kafka, Git, SQLite, or DNS service from scratch.

Start for free today and enjoy 40% off any paid plan by using this link.

Show Notes

About KSAT

KSAT, or Kongsberg Satellite Services, is a global leader in providing ground station services for satellites. The company slogan is “We Connect Space And Earth,” and their mission-critical services are used by customers around the world to access satellite data for a wide range of applications, including weather monitoring, environmental research, and disaster response.

About Vegard Sandengen

Vegard Sandengen is a Rust engineer at KSAT, where he works on the company’s data management systems. He has a Master’s degree in computer science and has been working in the space industry for several years.

At KSAT, Vegard focuses on building high-performance data processing pipelines that handle satellite telemetry and payload data from ground stations around the world. His work involves optimizing real-time data flows and ensuring system reliability for mission-critical space operations.

Links From The Episode

  • SpaceX - Private space exploration company revolutionizing satellite launches
  • CCSDS - Space data systems standardization body
  • Ground Station
  • Polar Orbit - Orbit with usually limited ground station visibility
  • TrollSat - Remote Ground Station in Antarctica
  • OpenStack - Build-your-own-cloud software stack
  • RustConf 2024: K2 Space Lightning Talk - K2 Space’s sponsored lightning talk, talking about 100% Rust based satellites
  • K2 Space - Space company building satellites entirely in Rust
  • Blue Origin - Space exploration company focused on reusable rockets
  • Rocket Lab - Small satellite launch provider
  • AWS Ground Station - Cloud-based satellite ground station service
  • Strangler Pattern - A software design pattern to replace legacy applications step-by-step
  • Rust by Example: New Type Idiom - Creating new wrapper types to leverage Rust’s type system guarantees for correct code
  • serde - Serialization and deserialization framework for Rust
  • utoipa - OpenAPI specification generation from Rust code
  • serde-json - The go-to solution for parsing JSON in Rust
  • axum - Ergonomic web framework built on tokio and tower
  • sqlx - Async SQL toolkit with compile-time checked queries
  • rayon - Data parallelism library for Rust
  • tokio - Asynchronous runtime for Rust applications
  • tokio-console - Debugger for async Rust applications
  • tracing - Application-level tracing framework for async-aware diagnostics
  • W3C Trace Context - Standard for distributed tracing context propagation
  • OpenTelemetry - Observability framework for distributed systems
  • Honeycomb - Observability platform for complex distributed systems
  • Azure Application Insights - Application performance monitoring service

Official Links

1Password

2025-06-26 08:00:00

Handling secrets is extremely hard. You have to keep them safe (obviously), while at the same time you need to integrate with a ton of different systems and always provide a great user-experience, because otherwise people will just find a way around your system. When talking to peers, a lot of people mention 1Password as a company that nailed this balance.

In today’s episode, I talk to Andrew about how 1Password uses Rust to build critical systems that must never fail, how Rust helps them handle secrets for millions of users, and the lessons they learned when adopting Rust in their stack.

Proudly Supported by CodeCrafters

CodeCrafters helps you become proficient in Rust by building real-world, production-grade projects. Learn hands-on by creating your own shell, HTTP server, Redis, Kafka, Git, SQLite, or DNS service from scratch.

Start for free today and enjoy 40% off any paid plan by using this link.

Show Notes

About 1Password

1Password is a password manager that helps users securely store and manage their passwords, credit card information, and other sensitive data. It provides a user-friendly interface and strong security features to protect users’ secrets across multiple devices.

About Andrew Burkhart

Andrew is a Senior Rust Developer at 1Password in the Product Foundations org, on the Frameworks team and specifically on the Core Platform squad handling the asynchronous frameworks other developers use to build features (i.e. requests into the Rust core from the Native clients, data sync, etc.). He specifically specialized in that synchronization process, getting data federated from cloud to clients to native apps and back.

Links From The Episode

  • Backend for Frontend Pattern - Architectural pattern for creating dedicated backends for specific frontends
  • typeshare - Generate types for multiple languages from Rust code
  • zeroizing-alloc - 1Password’s minimal secure heap zero-on-free implementation for Rust
  • arboard - Cross-platform clipboard manager written in Rust
  • passkey-rs - Pure Rust implementation of the WebAuthn Passkey specification
  • WebAssembly (WASM) - Binary instruction format for portable execution across platforms
  • tokio - The de facto standard async runtime for Rust
  • Clippy - A collection of lints to catch common mistakes in Rust
  • cargo-deny - Cargo plugin for linting dependencies, licenses, and security advisories
  • Nix - Purely functional package manager for reproducible builds
  • Nix Flakes - Experimental feature for hermetic, reproducible Nix builds
  • direnv - Load and unload environment variables based on current directory
  • ACM: Spotify Guilds - A study into Spotify’s Agile Model’s Guilds
  • axum - Ergonomic and modular web framework built on tokio and tower
  • tower - Library for building robust networking clients and servers
  • tracing - Application-level tracing framework for async-aware diagnostics
  • rusqlite - Ergonomic wrapper for SQLite in Rust
  • mockall - Powerful mock object library for Rust
  • pretty_assertions - Better assertion macros with colored diff output
  • neon - Library for writing native Node.js modules in Rust
  • nom-supreme - Parser combinator additions and utilities for nom
  • crane - Nix library for building Cargo projects
  • Rust in Production: Zed - High-performance code editor built in Rust
  • tokio-console - Debugger for async Rust programs using tokio
  • Rust Atomics and Locks by Mara Bos - Free online book about low-level concurrency in Rust
  • The Rust Programming Language (Brown University Edition) - Interactive version of the Rust Book with quizzes
  • Rustlings - Small exercises to get you used to reading and writing Rust code

Official Links

Tembo

2025-06-12 08:00:00

Recently I was in need of a simple job queue for a Rust project. I already had Postgres in place and wondered if I could reuse it for this purpose. I found PGMQ by Tembo. PGMQ a simple job queue written in Rust that uses Postgres as a backend. It fit the bill perfectly.

In today’s episode, I talk to Adam Hendel, the founding engineer of Tembo, about one of their projects, PGMQ, and how it came to be. We discuss the design decisions behind job queues, interfacing from Rust to Postgres, and the engineering decisions that went into building the extension.

It was delightful to hear that you could build all of this yourself, but that you would probably just waste your time doing so and would come up with the same design decisions as Adam and the team.

Proudly Supported by CodeCrafters

CodeCrafters helps you become proficient in Rust by building real-world, production-grade projects. Learn hands-on by creating your own shell, HTTP server, Redis, Kafka, Git, SQLite, or DNS service from scratch.

Start for free today and enjoy 40% off any paid plan by using this link.

Show Notes

About Tembo

Tembo builds developer tools that help teams build and ship software faster. Their first product, PGMQ, was created to solve the problem of job queues in a simple and efficient way, leveraging the power of Postgres. They since made a pivot to focus on AI-driven code assistance, but PGMQ can be used independently and is available as an open-source project.

About Adam Hendel

Adam Hendel is the founding engineer at Tembo, where he has been instrumental in developing PGMQ and other tools like pg_vectorize. He has since moved on to work on his own startup, but remains involved with the PGMQ project.

Links From The Episode

Official Links

Rust For Foundational Software

2025-06-06 08:00:00

Ten years of stable Rust; writing this feels surreal.

It’s only been yesterday that we all celebrated the 1.0 release of this incredible language.

The Rust Cologne/Bonn User Group wishes Rust 1.0 a Happy Birthday!

I was at Rust Week where Niko Matsakis gave his talk “Our Vision for Rust” in which he made a profound and insightful statement:

Rust is a language for building foundational software.

That deeply struck me.

I highly recommend you read his blog post titled “Rust in 2025: Targeting foundational software”, which is a great summary on the topic. I wanted to expand on the idea and share what this means to corrode (and perhaps to a wider extent to Rust in the industry).

The Issue With “Systems Programming”

First off, do we really need another term? After all, many people still think of Rust as a systems programming language first and foremost, so why can’t we just stick to “systems programming”?

I believe the framing is all wrong. From the outside, “systems programming” might establish that it is about “building systems,” but the term is loaded with historical baggage that feels limiting and prohibitive. It creates an artificial distinction between systems programming and “other types of programming.”

The mindset “We are not a systems programming company so we don’t need Rust” is common, but limiting.

If I may be candid for a moment, I believe well-known systems-programming domains have a tendency to be toxic. Even the best developers in the world have had that experience.

The first contribution that I had to the Linux kernel was some fix for the ext3 file system. It was a very emotional moment for me. I sent a patch to the Linux Kernel and then I saw an email response from Al Viro - one of those developers I’d only heard about and dreamed of meeting someday. He responded, ‘I’ve never seen code this bad in my life. You managed to introduce three new bugs in two new lines of code. People like you should never be allowed to get close to a keyboard again.’ That was my introduction to Linux.

– Glauber Costa, co-founder of Turso, on the Top Shelf Podcast

Glauber went on to work at Red Hat, Parallels, ScyllaDB, and Datadog on schedulers, databases, and performance optimizations, but just imagine how many capable developers got discouraged by similar early feedback or never even tried to contribute to the Linux kernel in the first place.

I find that ironic because people once dismissed Linux itself as just a toy project. The Linux kernel wasn’t built in a day. People need time to learn.

The whole idea of Rust is to enable everyone to build reliable and efficient software. To me, it’s about breaking down the barriers to entry and making larger parts of the software stack accessible to more people. You can sit with us.

We are committed to providing a friendly, safe and welcoming environment for all

The Rust Code of Conduct

That is also where the idea for corrode comes from: To cut through red tape in the industry. The goal is to gradually chip away at the crust of our legacy software with all its flaws and limitations and establish a better foundation for the future of infrastructure. To try and defy the ‘common wisdom’ about what the tradeoffs have to be. The term corrode is Latin for “to gnaw to bits, wear away.” 1

Where Rust Shines

“I think ‘infrastructure’ is a more useful way of thinking about Rust’s niche than arguing over the exact boundary that defines ‘systems programming’.”

“This is the essence of the systems Rust is best for writing: not flashy, not attention-grabbing, often entirely unnoticed. Just the robust and reliable necessities that enable us to get our work done, to attend to other things, confident that the system will keep humming along unattended.”

– Graydon Hoare, 10 Years of Stable Rust: An Infrastructure Story

In conversations with potential customers, one key aspect that comes up with Rust a lot is this perception that Rust is merely a systems programming language. They see the benefit of reliable software, but often face headwinds from people dismissing Rust as “yet another systems level language that is slightly safer.”

People keep asking me how Rust could help them. After all, Rust is just a “systems programming language.” I used to reply along the lines of Rust’s mantra: “empowering everyone to build reliable and efficient software” – and while I love this mission, it didn’t always “click” with people.

My clients use Rust for a much broader range of software, not just low-level systems programming. They use Rust for writing software that underpins other software.

Then I used to tell my own story: I did some C++ in the past, but I wouldn’t call myself a systems programmer. And yet, I help a lot of clients with really interesting and complex pieces of software. I ship code that is used by many people and companies like Google, Microsoft, AWS, and NVIDIA. Rust is a great enabler, a superpower, a fireflower.

Rust Is A Great Enabler

I found that my clients often don’t use Rust as a C++ replacement. Many clients don’t even have any C++ in production in the first place. They also don’t need to work on the hardware-software interface or spend their time in low-level code.

What they all have in common, however, is that the services they build with Rust are foundational to their core business. Rust is used for building platforms: systems which enable building other systems on top.

These services need to be robust and reliable and serve as platforms for other code that might or might not be written in Rust. This is, in my opinion, the core value proposition of Rust: to build things that form the bedrock of critical infrastructure and must operate reliably for years.

Rust is a day-2-language, i.e. it only starts to shine on day 2. All of the problems that you have during the lifecycle of your application surface early in development. Once a service hits production, maintaining it is boring. There is very little on-call work.

The focus should be on what Rust enables: a way to express very complicated ideas on a type-system level, which will help build complex abstractions through simple core mechanics: ownership, borrowing, lifetimes, and its trait system.

This mindset takes away the focus from Rust as a C++ replacement and also explains why so many teams which use languages like Python, TypeScript, and Kotlin are attracted by Rust.

What is less often talked about is that Rust is a language that enables people to move across domain boundaries: from embedded to cloud, from data science to developer tooling. Few other languages are so versatile and none offer the same level of correctness guarantees.

If you know Rust, you can program simple things in all of these domains.

Why Focus On Foundational Software?

But don’t we just replace “Systems Programming” with “Foundational Software”? Does using the term “Foundational Software” simply create a new limiting category?

Crucially, foundational software is different from low-level software and systems software. For my clients, it’s all foundational. For example, building a data plane is foundational. Writing a media-processing pipeline is foundational.

Rust serves as a catalyst: companies start using it for critical software but then, as they get more comfortable with the language, expand into using it in other areas of their business:

I’ve seen it play out as we built Aurora DSQL - we chose Rust for the new dataplane components, and started off developing other components with other tools. The control plane in Kotlin, operations tools in Typescript, etc. Standard “right tool for the job” stuff. But, as the team has become more and more familiar and comfortable with Rust, it’s become the way everything is built. A lot of this is because we’ve seen the benefits of Rust, but at least some is because the team just enjoys writing Rust.

Marc Brooker, engineer at Amazon Web Services in Seattle on lobste.rs

That fully aligns with my experience: I find that teams become ambitious after a while. They reach for loftier goals because they can. The fact they don’t have to deal with security issues anymore enables better affordances. From my conversations with other Rustaceans, we all made the same observation: suddenly we can build more ambitious projects that we never dared tackling before.

It feels to me as if this direction is more promising: starting with the foundational tech and growing into application-level/business-level code if needed/helpful. That’s better than the other way around, which often feels unnecessarily clunky. Once the foundations are in Rust, other systems can be built on top of it.

Just because we focus on foundational software doesn’t mean we can’t do other things. But the focus is to make sure that Rust stays true to its roots.

Systems You Plan To Maintain For Years

So, what is foundational software?

It’s software that organizations deem critical for their success. It might be:

  • a robust web backend
  • a package manager
  • a platform for robotics
  • a storage layer
  • a satellite control system
  • an SDK for multiple languages
  • a real time notification service

and many, many more.

All of these things power organizations and must not fail or at least do so gracefully. My clients and the companies I interviewed on our podcast all have one thing in common: They work on Rust projects that are not on the sideline, but front and center, and they shape the future of their infrastructure. Rust is useful in situations where the “worse is better” philosophy falls apart; it’s a language for building the “right thing”:

With the right thing, designers are equally concerned with simplicity, correctness, consistency, and completeness.

I think many companies will choose Rust to build their future platforms on. As such, it competes with C++ as much as it does with Kotlin or Python.

I believe that we should shift the focus away from memory safety (which these languages also have) and instead focus on the explicitness, expressiveness, and ecosystem of Rust that is highly competitive with these languages. It is a language for teams which want to build things right and are at odds with the “move fast and break things” philosophy of the past. Rust is future-looking. Backwards-compatibility is enforced by the compiler and many people work on the robustness aspect of the language.

Dropbox was one of the first production users of Rust. They built their storage layer on top of it. At no point did they think about using Rust as a C++ replacement. Instead, they saw the potential of Rust as a language for building scalable and reliable systems. Many more companies followed: Amazon, Google, Microsoft, Meta, Discord, Cloudflare, and many more. These organizations build platforms. Rust is a tool for professional programmers, developed by world experts over more than a decade of hard work.

Rust Is A Tool For Professionals

Is Rust used for real?

“At this point, we now know the answer: yes, Rust is used a lot. It’s used for real, critical projects to do actual work by some of the largest companies in our industry. We did good.”

“[Rust is] not a great hobby language but it is a fantastic professional language, precisely because of the ease of refactors and speed of development that comes with the type system and borrow checker.”

– Graydon Hoare, 10 Years of Stable Rust: An Infrastructure Story

To build a truly industrial-strength ecosystem, we need to remember the professional software lifecycle, which is hopefully decades long. Stability plays a big role in that. The fact that Rust has stable editions and a language specification is a big part of that.

But Rust is not just a compiler and its standard library. The tooling and wider ecosystem are equally important. To build foundational software, you need guarantees that vulnerabilities get fixed and that the ecosystem evolves and adapts to the customer’s needs. The ecosystem is still mostly driven by volunteers who work on important parts of the ecosystem in their free time. There is more to be said about supply-chain security and sustainability in the ecosystem.

Rust Is A Language For Decades

Building foundational systems is rooted in the profound belief that the efforts will pay off in the long run because organizations and society will benefit from them for decades. We are building systems that will be used by people who may not even know they are using them, but who will depend on them every day. Critical infrastructure.

And Rust allows us to do so with great ergonomics. Rust inherits pragmatism from C++ and purism from Haskell.

Rust enables us to build sustainable software that stays within its means and is concerned about low resource usage. Systems where precision and correctness matter. Solutions that work across language boundaries and up and down the stack.

Rust is a language for decades and my mission is to be a part of this shift.

On to the next 10 years!

  1. Conveniently, it also has a ‘C’ and an ‘R’ in the name, which bridges both languages.