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

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 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

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.

Rust

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.

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 Rust

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.

About Niko Matsakis

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.

Special Thanks

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.

Links From The Episode

Official Links

Sharp Edges In The Rust Standard Library

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.

Threading

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 std::thread;
use std::time::Duration;

struct Resource;

impl Drop for Resource {
    fn drop(&mut self) {
        // This never gets called
        println!("CRITICAL CLEANUP: Resource dropped");
    }
}

fn main() {
    let handle = thread::spawn(|| {
        let resource = Resource;
        // Do some work... 
        thread::sleep(Duration::from_secs(60));
        // Resource should be dropped here when thread ends
    });
    
    // Main thread exits immediately.
    // But! We forgot to join the spawned thread!
    // handle.join().unwrap();
}

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 makes thread::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 or VecDeque 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 std::collections::LinkedList;

fn main() {
    let mut list = LinkedList::from([1,2,3]);
    dbg!(list);
    
    // This is still unstable!
    // https://github.com/rust-lang/rust/issues/69210
    // The operation should compute in O(n) time.
    // Panics if out of range...
    // list.remove(0);
}

Even if you wanted a linked list, it probably would not be std::collections::LinkedList:

  • It doesn’t support O(1) splice, O(1) node erasure, or O(1) node insertion - only O(1) operations at the list ends
  • It has all the disadvantages of a doubly-linked list but none of its advantages
  • Custom implementations are often needed anyway. For example, many real-world use cases require an intrusive linked list implementation, not provided by std. An intrusive list is what the Linux kernel provides and even Rust for Linux has its own implementation of an intrusive list.
  • Arena-based linked lists are often needed for better performance.

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 Handling

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 std::path::Path;

fn main() {
    let relative_path = Path::new("relative/path");
    let absolute_path = Path::new("/absolute/path");

    // This will return "/absolute/path"
    let result = relative_path.join(absolute_path);
    assert_eq!(result, absolute_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 std::path::Path;

fn main() {
   let path = Path::new("/path/to/file.txt");
   
   // The awkward dance with multiple conversions
   match path.as_os_str().to_str() {
       Some(s) => {
           // Yay! We can use string operations
       },
       None => {
           // Oh well, it's not UTF-8.
           // Many developers just use lossy conversion to avoid dealing with this
           // Which might _silently_ corrupt path data but keeps the code moving...
           let lossy = path.to_string_lossy();
           
       }
   }
}

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 tedious
  • The design creates a poor experience for Windows users, with inefficient Path/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.

Platform-Specific Date and Time Handling

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 std::time::{Duration, SystemTime};

fn main() {
let now = SystemTime::now();
dbg!((now + Duration::from_nanos(1)).duration_since(now));
}

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 std::thread;
use std::time::Duration;

thread::sleep(Duration::from_secs(1));

…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.

Summary

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

  1. For a more in-depth discussion on why the Linux kernel uses linked lists, see this article.

  2. Perhaps this is a little surprising if you mostly use vectors, but removing an element from a list is an O(n) operation.

  3. “Hold on, what’s wrong with BTreeMap?” you might ask.

    This is just a mild observation rather than a strong criticism. If you're truly interested, expand the details by clicking here.

    Okay, as you know, Rust has two map implementations in the standard library: BTreeMap, which guarantees insertion ordering, while HashMap is unordered, but more commonly used. For a long time, a “performance trick” was to use BTreeMap if you needed a faster hash map implementation.

    Since then, the performance of HashMap has improved significantly. One reason is that the implementation of HashMap has changed to use a “siphash” algorithm and is now based on Google’s SwissTable. The combination of these changes has made HashMap much more performant than before, so there is no good reason to use BTreeMap anymore, other than the ordering guarantee.

    One important distinction to note: iteration order over a HashMap is random, while BTreeMap’s iteration order is always sorted by the key’s Ord implementation (not by insertion order). This makes BTreeMap useful when you need to iterate over keys in a sorted manner. If you’re aggregating data in a HashMap but need a sorted list, you’ll need to collect into a vector and sort it manually. In contrast, BTreeMap gives you sorted iteration for free. So while HashMap is better for random access operations, BTreeMap is still helpful when sorted iteration is required. I would argue that it’s a bit of a niche use case, however.

    In “Smolderingly fast b-trees”, Jamie Brandon compares the performance of Rust’s BTreeMap and HashMap. Here are the key takeaways:

    • When comparing performance, btrees were found to be significantly slower than hashmaps in most scenarios, especially for lookups. In the worst case with random-ish strings that share common prefixes, btrees performed dramatically worse.
    • Hashmaps benefit more from speculative execution between multiple lookups, while btrees don’t.
    • btrees have performance “cliffs” when comparisons get more expensive and touch more memory
    • For space usage, the author estimates that btrees would use >60% more memory than hashmaps for random keys.

    I’d argue that a normal HashMap is almost always the better choice and having two map implementations in the standard library can be confusing.

    On top of that, if the hash map is your bottleneck, you’re doing pretty well already. If you need anything faster, there are plenty of great external crates like indexmap for insertion-order preservation and dashmap for concurrent access.

    As I said, nothing earth-shaking, but I think it’s worth mentioning that there are better alternatives to BTreeMap out there in the ecosystem.

  4. Yes, you can deprecate functionality, but this is a very timid and laborious process and that still doesn’t mean functionality gets removed. For example, std::env::home_dir() has been deprecated for years and is now not getting removed, but instead will be fixed with a bugfix release and un-deprecated.

C++ to Rust Cheat-Sheet

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.

Comparing Idioms in Rust and C++

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; or
consteval int MAX_FN() { return 100; }
Function Declaration
fn add(first: i32, second: i32) -> i32 {
    first + second
}
int add(int first, int second) {
    return first + second;
}
Implicit Return
fn add(a: i32, b: i32) -> i32 { a + b }
auto add(int a, int b) -> int { return a + b; }
Immutable Reference &T const T&
Mutable Reference &mut T T&
Raw Pointer *const T
*mut T
const T*
T*
Struct Declaration
struct Person {
    id: u32,
    health: i32
}
struct Person {
    unsigned int id;
    int health;
};
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
impl MyClass {
    fn new(name: &String, data: &Vec) -> Self {
        // ...
    }
}
// Usually split between header (.h) and implementation (.cpp)
// Header:
class MyClass {
public:
    MyClass(const string& name, 
            const vector& data);
};
// Implementation:
MyClass::MyClass(const string& name, 
                const vector& data) {
    // ...
}
Method with Self
fn get_name(&self) -> String {
    self.name.clone()
}
string get_name() const {
    return name; // 'this' is implicit in C++
}
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
trait Shape {
    fn get_area(&self) -> f64;
}
class Shape {
public:
    virtual double get_area() const = 0;
};
Implementing Interface
impl Shape for Circle {
    fn get_area(&self) -> f64 {
        // ...
    }
}
class Circle : public Shape {
public:
    double get_area() const override {
        // ...
    }
};
Generic Function
fn generic_call(gen_shape: &T) {
    // ...
}
template
void generic_call(const T& gen_shape) {
    // ...
}
Associated Types
trait Shape {
    type InnerType;
    fn make_inner(&self) -> Self::InnerType;
}
// C++20 concepts approach
template
concept Shape = requires(T t) {
    typename T::InnerType;
    { t.make_inner() } -> 
        std::convertible_to;
};
Enums (Tagged Union)
enum MyShape {
    Circle(f64),
    Rectangle(f64, f64)
}
// Must specify contained types
std::variant my_shape;
// Uses integer tags rather than named variants
Pattern Matching
match shape {
    MyShape::Circle(r) => // ...,
    MyShape::Rectangle(w, h) => // ...
}
std::visit(overloaded {
    [](const Circle& c) { /* ... */ },
    [](const Rectangle& r) { /* ... */ }
}, my_shape);
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
// Manual allocation
let boxed = Box::new(value);
// Explicit non-trivial copies
let s = String::from("text");
let owned = borrowed.to_owned();
let cloned = original.clone();
// Manual allocation
auto* ptr = new T();
// Smart pointers
auto unique = std::make_unique();
auto shared = std::make_shared();
// Implicit non-trivial copies when passing by value
auto copy = original; // May implicitly copy
Destructors
impl Drop for MyType {
    fn drop(&mut self) {
        // cleanup
    }
}
~MyType() {
    // cleanup
}
Serialization #[derive(Serialize, Deserialize)] Requires manual implementation or code generation
(may change with C++26 reflection)
Print to Console println!("Hello, {name}"); std::cout
std::println("Hello, {}", name); (C++23)
Debug Output println!("{:?}", object); No direct equivalent; requires custom implementation
Pretty Debug Output println!("{:#?}", object); No direct equivalent

Rust vs C++ Type Equivalents

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
u8, u16, u32, u64, u128, usize
uint8_t, uint16_t, uint32_t, uint64_t, 
__uint128, uintptr_t/size_t
Signed Integers
i8, i16, i32, i64, i128, isize
int8_t, int16_t, int32_t, int64_t, 
__int128, intptr_t/ptrdiff_t
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.

Astral

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.