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.
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.
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.
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.
sub
part of of pubsubpub
part of of pubsub2025-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.
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).
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 Torso, 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
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
“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.
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.
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.
So, what is foundational software?
It’s software that organizations deem critical for their success. It might be:
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.
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.
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!
2025-05-29 08:00:00
Few developers have been as influential to my career as Niko Matsakis. Of course he is a world-class engineer with a PhD from ETH Zürich, a Rust core maintainer who has been working on the language for way more than a decade, and a Senior Principal Engineer at AWS. But more importantly, he is an empathetic human and an exceptional communicator.
I’ve personally been waiting for one year to get him on the show and steal one hour of his precious time. Now, finally, I got my chance at live recording at Rust Week 2025. The result is everything I hoped for: a trip down memory lane which takes us back to the early days of Rust, an honest and personal look at Rust’s strengths and weaknesses, and a glimpse into the future of the language. All of that packed with insightful anecdotes based on Niko’s decades of experience. If you like Rust, you will enjoy this episode.
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.
Rust is the language which brought us all together. What started as a side-project of Graydon Hoare, a single developer at Mozilla, has grown into a worldwide community of hobbyists, professionals, and companies which all share the same goal: to build better, safer software paired with great ergonomics and performance.
Niko is a long-time Rust core team member, having joined the project in 2012. He was and still is part of the team which designed and implemented Rust’s borrow checker, which is the language’s most important feature. He has been a voice of reason and a guiding light for many of us, including myself. His insightful talks and blog posts have helped countless developers to see the language and its goals in a new light.
Thanks to RustNL, the organizers of Rust Week 2025 for inviting Simon and me to record this episode live. They did a fantastic job organizing the event, and it was an honor to be part of it.
ret
for return
, cont
for continue
~T
and @T
as syntax features instead of Box<T>
and Rc<T>
std::threads
- Not green, just part of the standard librarystd::rc::Rc
- The @T
of std
std::boxed::Box
- The ~T
of std
with some special compiler saucestd::sync::Arc
- Thread safe Rc
pyo3::Py
- A pointer type in a different library!std::marker::Send
- A trait without even a method to dispatch, aptly placed in the marker
modulestd::marker::Sync
- Another example of a marker traitawait x
/ x.await
discussion?match
operator for this?2025-05-21 08:00:00
The Rust standard library, affectionately called std
, is exceptionally well-designed, but that doesn’t mean it’s perfect.
More experienced Rust developers tend to navigate around some of its sharper parts.
In this article, I want to highlight the areas in std
that I personally avoid.
Keep in mind that this list is subjective, so take it with a grain of salt.
My intention is to point out some pitfalls and suggest alternatives where appropriate.
Rust’s threading library is quite solid. That said, managing threads can be a bit of a footgun. In particular, forgetting to join a thread can have some unexpected side effects.
use thread;
use Duration;
;
In the above scenario, cleanup tasks (such as flushing caches or closing files) might not get executed.
So, even if you do nothing with the handle, it is still a best practice to join()
it.
For more details on the topic, check out matklad’s article: “Join Your Threads”.
In fact, there was a proposal to make std::thread::JoinHandle
be #[must_use]
, but it was ultimately declined because it would produce too many warnings.
This comment summarized the situation pretty well:
I’d say the key issue here is that
thread::spawn
is the easiest way of spawning threads, but not the best one for casual use. Manually calling.join().unwrap()
is a chore and easy to forget, which makesthread::spawn
a potential footgun.
For new code, I recommend using thread::scope
instead, which is a much better API in every conceivable way.
The documentation addresses the above issue directly:
Unlike non-scoped threads, scoped threads can borrow non-
'static
data, as the scope guarantees all threads will be joined at the end of the scope. All threads spawned within the scope that haven’t been manually joined will be automatically joined before this function returns.
Alternatively, you could use a thread pool library or rayon, in case you have an iterator you want to parallelize without manually managing threads.
std::collections::LinkedList
Implementing a linked list in Rust is not easy. That’s because Rust’s ownership model is detrimental to self-referential data structures.
Some people might not know that the standard library ships an implementation of a linked list at std::collections::LinkedList
.
In all those years, I never felt the urge to use it.
It might even be the least-used collection type in the standard library overall.
For all ordinary use cases, a Vec
is superior and straightforward to use.
Vectors also have better cache locality and performance characteristics: all items are stored in contiguous memory, which is much better for fast memory access.
On the other side, elements in a linked list can be scattered all over the heap.
If you want to learn more, you can read this paper, which contains some benchmarks.
You might be wondering why linked lists get used at all. They have their place as a very specialized data structure that is only really helpful in some resource-constrained or low-level environments like a kernel. The Linux kernel, for example, uses a lot of linked lists. The reason is that the kernel’s intrusive linked list implementation embeds list nodes directly within data structures which is very memory efficient and allows objects to be in multiple lists simultaneously without additional allocations. 1
As for normal, everyday code, just use a Vec
.
Even the documentation of LinkedList
itself agrees:
NOTE: It is almost always better to use
Vec
orVecDeque
because array-based containers are generally faster, more memory efficient, and make better use of CPU cache.
I believe the LinkedList should not have been included in the standard library in the first place. Even its original author agrees.
There are some surprising gaps in the API; for instance, LinkedList::remove
is still a nightly-only feature2:
use LinkedList;
Even if you wanted a linked list, it probably would not be std::collections::LinkedList
:
std
.
An intrusive list is what the Linux kernel provides
and even Rust for Linux has its own implementation of an intrusive list.There is a longer discussion in the Rust forum.
Better implementations exist that provide more of the missing operations expected from a proper linked list implementation:
There’s a thing to be said about BTreeMap
as well, but I leave it at that. 3
Path
does a decent job of abstracting away the underlying file system.
One thing I always disliked was that Path::join
returns a PathBuf
instead of a Result<PathBuf, Error>
.
I mentioned in my ‘Pitfalls of Safe Rust’ article
that Path::join
joining a relative path with an absolute path results in the absolute path being returned.
use Path;
I think that’s pretty counterintuitive and a potential source of bugs.
On top of that, many programs assume paths are UTF-8 encoded and frequently convert them to str
.
That’s always a fun dance:
use Path;
These path.as_os_str().to_str()
operations must be repeated everywhere.
It makes path manipulation every so slightly annoying.
There are a few more issues with paths in Rust:
Path
/OsStr
lacks common string manipulation methods (like find()
, replace()
, etc.), which makes many common operations on paths quite tediousPath
/OsStr
handling that doesn’t fit the platform well. It’s a cross-platform compromise, but it creates some real problems.Of course, for everyday use, Path
is perfectly okay, but if path handling is a core part of your application, you might want to consider using an external crate instead.
camino
is a good alternative crate, which just assumes that paths are UTF-8 (which, in 2025, is a fair assumption).
This way, operations have much better ergonomics.
In my opinion, it’s actually great to have some basic time functionality right in the standard library.
However, just be aware that std::time::SystemTime
is platform dependent, which causes some headaches.
Same for Instant
, which is a wrapper around the most precise time source on each OS.
Since time is such a thin wrapper around whatever the operating system provides, you can run into some nasty behavior. For example, this does not always result in “1 nanosecond” on Windows:
use ;
The documentation does not specify the clock’s accuracy or how it handles leap seconds, except to note that SystemTime
does not account for them.
If you depend on proper control over time, such as managing leap seconds or cross-platform support, you’re better off using an external crate. For a great overview, see this survey in the Rust forum, titled: ‘The state of time in Rust: leaps and bounds’.
In general, I believe std::time
works well in combination with the rest of the standard library, such as for sleep
:
use thread;
use Duration;
;
sleep
…but apart from that, I don’t use it for much else. If I had to touch any sort of date calculations, I would defer to an external crate.
There are a few options:
jiff
is a new library, which aims to improve upon the existing libraries.
As you can see, my list of warts in the Rust standard library is quite short. Given that Rust 1.0 was released more than a decade ago, the standard library has held up really well. That said, I reserve the right to update this article in case I become aware of additional sharp edges in the future.
In general, I like that Rust has a relatively small standard library because once a feature is in there it stays there forever. 4
2025-05-17 08:00:00
Some people learn new programming languages best by looking at examples for how to do the same thing they know in one language is done in the other. Below is a syntax comparison table which can serve as a quick reference for common C++ constructs and their equivalents in Rust. It is not a comprehensive guide, but I hope it helps out a C++ developer looking for a quick reference to Rust syntax.
Feature | Rust | C++ |
---|---|---|
Immutable Variable Declaration |
let x: i32 = 5; (immutable by default) |
const int x = 5; |
Mutable Variables | let mut x = 5; |
int x = 5; (mutable by default) |
Type Inference | let x = 5; |
auto x = 5; |
Constant Declaration | const MAX: i32 = 100; |
constexpr int MAX = 100; orconsteval int MAX_FN() { return 100; }
|
Function Declaration |
|
|
Implicit Return |
|
|
Immutable Reference | &T |
const T& |
Mutable Reference | &mut T |
T& |
Raw Pointer |
*const T *mut T
|
const T* T*
|
Struct Declaration |
|
|
Struct Initialization | Person { id: uid, health: 100 } |
Person{uid, 100} or Person{.id = uid, .health = 100} (multiple initialization styles available in C++) |
Struct Field Access | person.id |
person.id |
Class/Method Implementation |
|
|
Method with Self |
|
|
Static Method | fn static_method() { /* ... */ } |
static void static_method() { /* ... */ } Note: 'static' in C++ has multiple meanings including file-scope lifetime, class-level function, and non-exported symbols |
Interface/Trait |
|
|
Implementing Interface |
|
|
Generic Function |
|
|
Associated Types |
|
|
Enums (Tagged Union) |
|
|
Pattern Matching |
|
|
Optional Types |
Option<T> (Some(T) or None) |
std::optional<T> |
Error Handling |
Result<T, E> (Ok(T) or Err(E)) |
std::expected<T, E> (C++23) |
Error Propagation | let file = File::open("file.txt")?; |
No direct equivalent; uses exceptions or return codes |
Automatic Trait Implementation | #[derive(Debug, Clone, PartialEq)] |
No direct equivalent (may change with C++26 reflection) |
Memory Management |
|
|
Destructors |
|
|
Serialization | #[derive(Serialize, Deserialize)] |
Requires manual implementation or code generation (may change with C++26 reflection) |
Print to Console | println!("Hello, {name}"); |
std::cout |
Debug Output | println!("{:?}", object); |
No direct equivalent; requires custom implementation |
Pretty Debug Output | println!("{:#?}", object); |
No direct equivalent |
Type | Rust | C++ |
---|---|---|
Boolean | bool |
bool |
Character |
char (Unicode scalar value, 4 bytes) |
char32_t (or wchar_t on some platforms) |
Byte (Raw Data) |
u8 (always unsigned) |
uint8_t or unsigned char (for guaranteed unsigned) |
Unsigned Integers |
|
|
Signed Integers |
|
|
Floating Point | f32, f64 |
float, double |
Unit Type |
() (unit) |
void |
Never Type |
! (never) |
[[noreturn]] void |
Immutable Reference | &T |
const T& |
Mutable Reference | &mut T |
T& |
Raw Pointers |
*const T , *mut T
|
const T* , T*
|
Arrays | [T; N] |
std::array<T, N> or T[N]
|
Slices | &[T] |
std::span<T> (C++20) |
Dynamic Array | Vec<T> |
std::vector<T> |
String |
String (UTF-8 encoded) |
std::string |
String Slice |
&str (UTF-8 encoded) |
std::string_view (C++17) |
C Compatible String |
CString , &CStr
|
std::string , const char*
|
Nullable/Optional | Option<T> |
std::optional<T> (C++17) |
Error Handling | Result<T, E> |
std::expected<T, E> (C++23) |
Smart Pointer (Unique) | Box<T> |
std::unique_ptr<T> |
Smart Pointer (Shared) |
Rc<T> (single-threaded)Arc<T> (thread-safe) |
std::shared_ptr<T> |
Weak Pointer |
Weak<T> (from Rc/Arc) |
std::weak_ptr<T> |
Hash Map | HashMap<K, V> |
std::unordered_map<K, V> |
Ordered Map | BTreeMap<K, V> |
std::map<K, V> |
Hash Set | HashSet<T> |
std::unordered_set<T> |
Ordered Set | BTreeSet<T> |
std::set<T> |
Double-Ended Queue | VecDeque<T> |
std::deque<T> |
Linked List | LinkedList<T> |
std::list<T> |
Priority Queue | BinaryHeap<T> |
std::priority_queue<T> |
Tuple | (T1, T2, ...) |
std::tuple<T1, T2, ...> |
Tagged Union | enum Variant { A(T1), B(T2), ... } |
std::variant<T1, T2, ...> (C++17) |
Interior Mutability |
Cell<T> , RefCell<T>
|
mutable keyword |
Thread-Safe Mutability |
Mutex<T> , RwLock<T>
|
std::mutex + T
|
Atomic Types |
AtomicBool , AtomicUsize , etc. |
std::atomic<bool> , std::atomic<size_t> , etc. |
2025-05-15 08:00:00
Up until a few years ago, Python tooling was a nightmare: basic tasks like installing packages or managing Python versions was a pain. The tools were brittle and did not work well together, mired in a swamp of underspecified implementation defined behaviour.
Then, apparently suddenly, but in reality backed by years of ongoing work on formal interoperability specifications, we saw a renaissance of new ideas in the Python ecosystem. It started with Poetry and pipx and continued with tooling written in Rust like rye, which later got incorporated into Astral.
Astral in particular contributed a very important piece to the puzzle: uv
– an extremely fast Python package and project manager that supersedes all previous attempts;
For example, it is 10x-100x faster than pip.