Latest Posts (20 found)
Corrode 1 weeks ago

Migrating from Go to Rust

Out of all the migrations I help teams with, Go to Rust is a bit of an outlier. It’s not a question of “is Rust faster?” or “does Rust have types?”, Go already gets you most of the way there. The discussion is mostly about correctness guarantees , runtime tradeoffs , and developer ergonomics . A quick disclaimer before we start: this guide is heavily backend-focused . Backend services are where Go is strongest, small static binaries, a standard library focused on networking, and an ecosystem of libraries for HTTP servers, gRPC, databases, etc. That’s also where most teams considering Rust are coming from (at least the ones who reach out to me), so I think that’s the comparison that’s actually useful in practice. If you’re writing CLI tools, embedded firmware, or game engines, some of this still applies, but to be honest, I’m afraid this is not the best resource for you. For context, I’ve written about Go and Rust before: “Go vs Rust? Choose Go.” back in 2017, and later the “Rust vs Go: A Hands-On Comparison” with the Shuttle team, which walks through a small backend service in both languages. What you will learn in this article I’ll be upfront: I’m not a fan of Go. I think it’s a badly designed language, even if a very successful one. It confuses easiness with simplicity , and several of its core design tradeoffs ( everywhere, error handling as a discipline rule rather than a type, the long absence of generics) point in a direction I disagree with. That said, success matters! Go has captured a real and persistent share of working developers, hovering around 17–19% in the JetBrains Developer Ecosystem Survey. Rust is growing steadily but is still a smaller slice: Go is clearly working for a lot of people, and a guide that pretends otherwise isn’t helpful. So I’ll do my very best to be objective in this guide rather than relitigate old arguments. But you should know my priors so you can calibrate. The other prior worth disclosing: I run a Rust consultancy; of course I’m biased! More people using Rust is good for my business. But I’ve also worked in both languages professionally and shipped Go services to production. This guide is for Go developers who want an honest, side-by-side look at what changes when you move to Rust. For a deliberately opposite take, I recommend reading “Just Fucking Use Go” by Blain Smith. Holding both views in your head at once is more useful than either one alone. If you prefer to watch rather than read, here’s a video from the Shuttle article above, read and commented by the Primeagen: Go developers already have one of the cleanest toolchains in the industry. Back in the day, it started off a trend of “batteries included” toolchains that give you a single, consistent interface for building, testing, formatting, linting, and managing dependencies. I’m glad that Rust followed suit, because it’s a great model. It’s one of my favorite parts about both ecosystems. has even more built-in: The big difference is that in Go you typically reach for third-party tools ( , , , ) to fill gaps. In Rust, the first-party ecosystem covers more out of the box. Things that do require external crates (e.g. , ) install with one command and feel native, e.g. gives you right away. Both communities have converged on the same insight about formatters: a single canonical style, even an imperfect one, is worth more than the bikeshedding it eliminates. Gofmt’s style is no one’s favorite, yet gofmt is everyone’s favorite. — Rob Pike, Go Proverbs The same is true of : not everyone likes every detail, but the absence of style debates in code review is worth far more than the occasional formatting preference you’d have made differently. The headline is that Go and Rust are both compiled, statically typed, single-binary-deploy languages with strong concurrency stories. The differences are about what guarantees you get from the compiler and how much control you have over runtime behaviour . Go developers don’t usually come to Rust because Go is “too slow.” For most backend workloads, Go is plenty fast. People are generally a bit frustrated with Go’s verbose error handling, the danger of segmentation faults from pointers, and the lack of generics (for a long time) or any sophisticated type system features, such as enums or traits. Interfaces are not a worthy replacement for traits, and the Go standard library has some weird gaps, such as the lack of a type. I call it my billion-dollar mistake. It was the invention of the null reference in 1965 … This has led to innumerable errors, vulnerabilities, and system crashes, which have probably caused a billion dollars of pain and damage in the last forty years. — Tony Hoare, inventor of , QCon London 2009 This is the one I hear most often. You ship a Go service, it runs fine for months, and then one Tuesday at 3 a.m. a code path runs where someone forgot to check whether a pointer was , and the goroutine panics. Go’s compiler does not force you to consider the absence case. Rust’s does: You literally cannot dereference an without acknowledging the case. Whole categories of pager-duty incidents disappear. is a great tool, but it’s a runtime detector, it only finds races that actually execute during your tests. Mutating a map from two goroutines without a lock compiles fine in Go and only blows up in production under load. In Rust, sharing mutable state across threads requires types that implement and . Try to share a plain between threads and the program does not compile . You’re forced to wrap it in an , an , or use a channel. That race condition becomes a type error. 1 is fine for a while. After a few years, you notice three things: It’s worth being honest about the counter-argument here, since it came up in the Lobste.rs thread on my Shuttle article: experienced Go developers point out that and catch most of the “forgot to handle the error” cases in practice, and that explicit is easier to read than dense chains. Both points are fair, and the explicit style is a deliberate cultural value, not an accident: I think that error handling should be explicit, this should be a core value of the language. — Peter Bourgon, GoTime #91 , quoted in Dave Cheney’s Zen of Go My take is that lints are an opt-in safety net you have to remember to set up, while Rust’s is the type signature itself, there’s no way to forget. The boilerplate-vs-readability tradeoff is more genuinely subjective. The operator handles propagation; handles wrapping; and a on is exhaustively checked . Add a new variant tomorrow and the compiler shows you every place that needs updating. Go got generics in 1.18, and they’re useful, but the implementation has constraints (no methods with type parameters, GC shape stenciling, occasional surprising performance characteristics). Rust generics monomorphize, each instantiation produces specialized code with zero runtime cost. Combined with traits, this gives you real zero-cost abstractions. This matters less in handler code and more in shared infrastructure (middleware, generic repositories, decoders, parsers), where Go often pushes you back to / plus type assertions. Go’s GC is excellent, concurrent, low-pause, well-tuned for typical service workloads. But “low-pause” is not “no-pause.” Under heavy allocation, P99 latency tails are noticeably worse than a Rust equivalent that simply doesn’t allocate on the hot path. I won’t oversell this, for the vast majority of services, Go’s GC is a non-issue. But for latency-sensitive systems (trading, real-time bidding, network proxies, high-throughput ingestion), the lack of GC pauses is a genuine selling point. Go is death by a thousand paper cuts. It is a very pragmatic language and if you are willing to glance over the above issues, you can be very productive in it. But at a certain codebase size, the problems start to compound. There is no single moment when Go loses its appeal, but teams find themselves wishing for more (more safety, more control, more expressiveness) and that’s when they start looking around for alternatives. The fastest way to feel comfortable in Rust is to map patterns you already know. For a longer, fully-worked example of building the same backend service in both languages, see the Shuttle comparison , the section below focuses on the patterns that come up most often. The operator does the dance for you, including type conversion if is implemented (idiomatic with ’s ). There is no in safe Rust. References can’t be null. Pointers can be, but you almost never use raw pointers in application code. Go’s interfaces are structural, a type satisfies an interface implicitly: Rust’s traits are nominal, you implement them explicitly: The Go style is great for ad-hoc duck typing. The Rust style is great for refactoring and discoverability, you can grep for every implementer of a trait. The closest equivalent of / in Rust is , but you almost never want it. The Go community knows the cost of reaching for too: interface{} says nothing. — Rob Pike, Go Proverbs Generic functions with trait bounds ( ) cover the vast majority of cases and give you monomorphization with no runtime dispatch. Where Go pre-1.18 would have forced you back to plus a type assertion, Rust’s traits + generics let you stay specific. When you do want runtime dispatch (e.g. heterogeneous storage of different implementers), reach for or . That’s the direct Rust analog of holding an value in Go. Go’s concurrency model is famously simple: Goroutines are cheap, the runtime schedules them across OS threads, and channels ( ) are the primary coordination primitive. The Go proverb captures the philosophy: Don’t communicate by sharing memory; share memory by communicating. — Rob Pike, Go Proverbs This is the area where Go genuinely shines, several commenters in the Lobste.rs discussion made the point that goroutines “just disappear” into normal-looking blocking code, and that’s worth giving Go credit for. Rust async is more powerful, but it’s also more visible in your code. Rust uses / on top of an executor (almost always for backend services): The shape is similar. The differences: For most backend code, the day-to-day feel is similar: spawn a task, communicate via channels, use timeouts liberally. In Go, you plumb a through every blocking call: Rust has no built-in . The closest equivalent for cancellation is : For timeouts, wraps any future. For deadlines/values, you typically pass them as explicit arguments or via spans rather than a single context object. Some Go developers miss the implicit-feel of . In practice, the explicit Rust style is easier to reason about, you always know exactly what’s cancellable and what isn’t. The deeper point is that neither language gives you cancellation for free, the discipline just shows up at different layers: Go doesn’t have a way to tell a goroutine to exit. There is no stop or kill function, for good reason. If we cannot command a goroutine to stop, we must instead ask it, politely. — Dave Cheney, The Zen of Go In Go that “asking politely” is a plumbed through every call site by convention. In Rust it’s a (or a channel) plumbed through every call site, but the compiler can actually tell you when you forgot. Both languages have channels. The translation is direct: Rust’s channels distinguish sender and receiver as separate types, which makes ownership and -ness explicit at the type level. Rust’s is the equivalent of a Go value receiver; is a pointer receiver with mutation. Owned (consuming the value) has no Go analog and is occasionally very useful (typestate, builders). Go’s is a UTF-8 byte slice with copy-on-assign semantics (the header is copied, the underlying bytes are shared and immutable). Rust splits this into two types: As a rule of thumb, take in arguments, return when you produce new data. This is mostly painless once you internalize it. The vs split is a microcosm of Rust’s broader “borrow vs own” model. Go got generics in 1.18 (March 2022), thirteen years after the language shipped. They are useful, but they feel tacked on, and in practice they have most of the downsides of a generic type system without delivering the upsides you’d expect coming from Rust, Haskell, or even modern C++. This is a strong claim, so let me back it up. The most telling signal is that three years after generics landed, Go’s own standard library still mostly avoids them. still takes a closure instead of a constraint. is still typed as / . The generic helpers that do exist live in a small handful of packages: , , , and a few entries under . Compare that to Rust, where generics permeate the standard library from day one: , , , , , / , , , every collection, every smart pointer. You cannot write idiomatic Rust without using generics, because the standard library is generic. In Go, generics are an opt-in feature for library authors who really need them. In Rust, they’re the substrate everything else is built on. Rust’s generics are tied to traits, which double as the language’s mechanism for ad-hoc polymorphism, supertraits, associated types, blanket impls, and coherence. Go’s constraints are just interfaces with an extra operator for type-set membership. There are no: The practical consequence is that the moment your abstraction needs more than “a function that works for any with these few operations,” Go pushes you back to plus type assertions, code generation, or runtime reflection. Rust uses a Hindley-Milner-style inference engine that propagates type information through entire expressions, including across closures, iterator chains, and operators. You routinely write: and the compiler figures out is from the range, and is from the target. Go’s inference is much shallower. It can usually infer type parameters from function arguments, but it cannot infer from return-position context , cannot chain inference through generic builders the way Rust does, and frequently forces explicit type arguments at call sites: In Rust this is the exception; in Go it’s still common. Rust monomorphizes: every and produces specialized machine code with zero runtime dispatch. Go uses GCShape stenciling with dictionaries , where types that share a “GC shape” share the same compiled function and dispatch through a dictionary at runtime. The result is a compile-time/runtime tradeoff that often surprises people: generic Go code can be measurably slower than the equivalent hand-written non-generic version, because every method call on a type parameter goes through an indirection. There’s a well-known PlanetScale post showing exactly this. In Rust, generic code is the fast path. Reaching for (the equivalent of Go’s interface dispatch) is a deliberate choice you make when you want runtime polymorphism. This is the part that bothers me most. A good generics system removes reasons to fall back to escape hatches. In Rust, generics + traits eliminate most of what you’d otherwise need or runtime reflection for. The type system gets stronger. In Go, generics did not remove , did not remove , did not remove code generation as the dominant pattern for things like ORMs, decoders, and mocks. still uses reflection. still uses . still generates code. The places where a real generics system would shine are the same places Go reaches for runtime mechanisms it had before 1.18. Generics in Go feel additive, a new tool in the box that’s useful in narrow cases. Generics in Rust feel foundational; remove them and the language collapses. That’s the difference, and it’s why generic Go code, in my experience, doesn’t read better than the -based code it replaced; it just reads differently, with more punctuation. If you’re already opinionated in Go, the Rust ecosystem has converged to a similar level of “default picks.” For a typical backend service: + + + + + covers 90% of what you need. I want to be straightforward here. Coming from Go, you will hit a wall . The wall has a name. Go’s runtime handles memory and aliasing for you. Rust pushes that decision into the type system. The first few weeks you’ll write code that “should obviously work” and the compiler will refuse it. The patterns that bite Go developers most often: With all of these rules, the borrow checker truly sounds like a “gatekeeper” of sorts, which keeps getting in the way and is just overall frustrating to deal with. That is not the mental mindset you should have when learning Rust. The borrow checker truly uncovers real and very existing bugs in your code, and if you don’t address them, your program will deal with safety issues. So whenever you get a compiler error from , take a step back and think how your code could break. A few questions you can ask yourself: That is the mindset you need to understand the borrow checker. Humans are genuinely bad at reasoning about memory. We forget that pointers can be null, that old references can outlive the data they point to, and that multiple threads can touch the same data at the same time. We tend to have a “linear” mental model of how data flows through a program, but in reality it’s closer to a complex graph with many paths and interactions. Every condition forces you to consider what happens in both branches. Every loop forces you to consider what happens on every iteration. That is exactly the kind of reasoning the borrow checker is designed to do for you! It enforces best practices at compile time, and it can feel annoying when your own mental model disagrees with the borrow checker’s (which is the more accurate one 99% of the time). There are cases where the borrow checker is genuinely too strict, but they are rare, and as a beginner you’ll almost never run into them. I got memory management wrong plenty of times in my early days, but I approached it with a learner’s mindset , which helped me ask “what’s wrong with my code?” instead of “what’s wrong with the compiler?”, a reaction I see a lot in trainings. The good news is that once you internalize borrowing, it stops fighting you. Most experienced Rust developers will tell you the borrow checker became an ally somewhere between weeks 4 and 12. The first month is the hardest. Be honest with your team, Rust compile times are a real downgrade from Go’s. A clean release build of a medium service can take minutes in comparison to Go’s near-instantaneous compiles. Incremental builds and are reasonable and compile times have gotten much better over the years, but you’ll feel the difference. To mitigate, use in your edit loop, split into a workspace once it pays off, and keep proc-macro-heavy crates in their own crate so they only recompile when they change. See tips for faster Rust compile times for a deeper dive. Go’s “one type of function, sync everywhere, the runtime handles concurrency” is genuinely simpler than Rust’s split between and . You’ll need to think about which of your functions are async, where you , and how that interacts with traits. Async traits (stable since Rust 1.75) help a lot, but there are still rough edges (especially around with async methods). Rust’s crate ecosystem is growing and libraries are high-quality across the board, but Go has a head start in some backend-adjacent domains: Kubernetes operators, cloud-provider SDKs, database drivers for certain niche stores. Before you commit, spend a day checking that the libraries you depend on have Rust equivalents you’re willing to use. Teams I help often have to hand-roll at least one or two core libraries themselves. For example, they might have to update an abandoned crate for XML schema validation, or write their own client for a lesser-known protocol. You don’t have to rewrite everything in one go. The strategies that work best, in order of how I usually recommend them: If one specific service in your fleet is the perpetual problem child (high CPU, latency-sensitive, or constantly hit with reliability issues), rewrite just that one in Rust, behind the same API contract. This is the lowest-risk migration. Other Go services keep talking to it via HTTP/gRPC, oblivious to the underlying language. Background workers, queue consumers, ingestion pipelines, and CPU-bound batch jobs are excellent first targets. They typically have a clear input/output boundary (a queue, a topic) and no shared in-process state with the rest of the system. You can call Rust from Go via cgo, and there are good guides on how to do it . (Reach out if you’d be interested in a guide on this from me.) In practice, I rarely recommend it for backend services. The build complexity and FFI overhead usually outweigh the benefits compared to “just stand up a Rust service and put it behind a network call.” For libraries and CLI tools, it’s more viable. If you have an API gateway or reverse proxy, you can route specific endpoints to a new Rust service while the rest stays in Go. This works particularly well when one bounded context (auth, search, billing) is the right unit to migrate. The pattern is often called “strangler fig,” because the new service grows around the old one until it eventually replaces it entirely. Start with a service that has a clear boundary. Don’t pick the most central, most-deployed service in your fleet. Pick the one where the contract with the rest of the system is well-defined and the blast radius is small. Keep the same API contract. If your Go service exposes a REST API, your Rust service should too: same paths, same JSON shapes, same error envelope. The migration is invisible to clients, and you can swap traffic incrementally with a gateway. Don’t translate idioms verbatim. Resist the urge to write Go-flavoured Rust. becomes . Goroutine-per-request becomes only when you actually need it (axum already concurrently handles requests). Interfaces with one method usually become trait bounds on a generic, not . Use the compiler as a pair programmer. Rust’s compiler errors are usually pretty good. Read them slowly. They almost always tell you the right answer. The team members who struggle longest are the ones who fight the compiler instead of treating it as a collaborator. Invest in training early. I’ve seen teams try to do a Rust migration “on the side,” learning as they go. It rarely ends well. It’s a bit like training for a marathon by signing up for the race and then trying to run it without any prior training. You can do it, but it’s going to be painful and you might not finish. Block off real time for learning: a workshop, an online course , paired sessions on real code. The upfront investment pays back many times over once the team is fluent. (Hey, if you want to talk about training options, I’m happy to chat .) Not everything should be migrated. Go is excellent for: A hybrid strategy is fine and common. Many of the teams I work with end up with a polyglot backend: Go for the “boring” services, Rust for the ones where reliability and performance pay back the extra effort. Numbers vary wildly by workload, so take these as rough guidance. Not promises! But here are some ballpark numbers, based on Go-to-Rust migrations I’ve helped with: Honestly, you’re unlikely to get a 10x throughput improvement going from Go to Rust the way you might from Python. What you get is fewer “silly errors” and flatter latency tails, plus the ability to expand into other domains like embedded development or systems programming while still using the same language. That’s often the most surprising side-effect of a migration: there’s a lot of opportunity for code-sharing across teams that previously had to use different stacks. You can use Rust for everything. Going from Go to Rust is a different kind of migration than coming from Python or TypeScript . Coming from Go, you know the benefits of a statically-typed, compiled language. So you’re not trading away dynamic typing or a slow runtime, you’re trading away in exchange for a more robust codebase with fewer footguns, and a stricter compiler that catches more mistakes at compile time. There is a steeper learning curve, however. For foundational services (services that your organization relies on, that have high uptime requirements, that are critical to your business), that trade is obviously worth it. For others, Go remains the right answer. The point of a migration is to put each problem in the language that solves it best. Ready to Make the Move to Rust? I help backend teams evaluate, plan, and execute Go-to-Rust migrations. Whether you need an architecture review, training, or hands-on help porting a critical service, let’s talk about your needs . Rust’s type system doesn’t catch all data races, but types that truly can’t be shared between threads without synchronization won’t compile. You can still have logic bugs in your synchronization, but you won’t have the kind of “oh no, I forgot to lock this” that often leads to silent data corruption. ↩ Where Go and Rust overlap, and where they diverge. How Go patterns map to Rust. What you gain from the borrow checker. Where I tell people to keep Go and where Rust is worth the migration cost. How to migrate Go services incrementally. The boilerplate dilutes the actual logic of your function. Wrapping with is a discipline rule, not a compiler rule. It’s easy to drop context on the floor. Sentinel errors via / work, but the compiler doesn’t tell you when you forgot to handle a new variant. Rust async functions return s. They don’t run until awaited or spawned. The compiler tracks / across points. If you hold a non- value across an await, you get a compile error explaining exactly why. There’s no built-in goroutine-style preemption. Long CPU-bound work in an async task starves the executor; you offload to or instead. Channels ( , , ) are first-class but live in libraries, not the language. , owned, heap-allocated, growable. Equivalent to you intend to mutate. , a borrowed view into someone else’s string data. Equivalent to a Go parameter most of the time. Supertraits / constraint hierarchies. In Rust you write , and any automatically satisfies and . Go has no equivalent; you stack interface embeddings, but the constraint solver doesn’t reason about hierarchies the way Rust’s trait system does. Associated types. Rust’s has , so is a first-class thing you can name in bounds. Go’s closest equivalent is a second type parameter, which leaks into every signature. Blanket impls. In Rust, automatically gives every type a method. Go has no way to add methods to a type from outside its defining package, generic or not. Methods with their own type parameters. This is an explicit, documented non-feature in Go. You cannot write . In Rust, generic methods on generic types are routine. Long-lived references. In Go, you’d happily hold a from a map for as long as you want. In Rust, that borrow blocks mutation of the map for its whole lifetime. The fix is usually to clone, or to scope the borrow tighter. Self-referential structs. Common in Go (a struct holding both data and an iterator over it). In Rust, this requires , , or a redesign. Almost always: redesign. Sharing mutable state across goroutines. What you’d write as becomes . Slightly more verbose, much more checked. Returning references from functions. Lifetime annotations show up. They’re not as bad as their reputation, but they’re new. If a value got moved from one place to another, what would happen if the original place tried to use it again? If a value is shared across threads, what would happen if one thread modified it while another thread is using it? If a pointer is dereferenced , what would happen if it was null or dangling? When a value goes out of scope , what would happen if it was still being used somewhere else? Kubernetes-native tooling : operators, controllers, CRDs. The ecosystem is overwhelmingly in Go. CLI utilities and dev tooling : fast compiles, easy cross-compilation, simple deployment. Glue services : thin API layers, proxies, format converters. The boilerplate ratio in Rust isn’t worth it here. Anywhere your team velocity matters more than absolute correctness guarantees . CPU usage: 20–60% reduction. Less dramatic than Python-to-Rust, because Go is already efficient. The wins come from no GC and tighter loops. Memory: 30–50% reduction, mostly from the absence of GC overhead and a smaller runtime. P99 latency: significantly more consistent. Rust services tend to flatline where Go services have visible GC-induced jitter. (This has gotten much better on the Go-side ever since they introduced their low-latency GC, but the difference is still there under heavy load.) Production incidents: this is the one teams report most enthusiastically. The classes of bugs that survive and reach production (data races, nil dereferences, missed error paths) just don’t compile in Rust. Oncall rotations are typically very boring after a Rust migration. Rust’s type system doesn’t catch all data races, but types that truly can’t be shared between threads without synchronization won’t compile. You can still have logic bugs in your synchronization, but you won’t have the kind of “oh no, I forgot to lock this” that often leads to silent data corruption. ↩

0 views
Corrode 1 months ago

Bugs Rust Won't Catch

In April 2026, Canonical disclosed 44 CVEs in uutils, the Rust reimplementation of GNU coreutils that ships by default since 25.10. Most of them came out of an external audit commissioned ahead of the 26.04 LTS. I read through the list and thought there’s a lot to learn from it. What’s notable is that all of these bugs landed in a production Rust codebase, written by people who knew what they were doing, and none of them were caught by the borrow checker, clippy lints , or cargo audit . I’m not writing this to criticize the uutils team. Quite the contrary; I actually want to thank them for sharing the audit results in such detail so that we can all learn from them. We also had Jon Seager, VP Engineering for Ubuntu, on our ‘Rust in Production’ podcast recently and a lot of listeners appreciated his honesty about the state of Rust at Canonical. If you write systems code in Rust, this is the most concentrated look at where Rust’s safety ends that you’ll likely find anywhere right now. This is the largest cluster of bugs in the audit. It’s also the reason , , and are still GNU in Ubuntu 26.04 LTS. :( The pattern is always the same. You do one syscall to check something about a path, then another syscall to act on the same path. Between those two calls, an attacker with write access to a parent directory can swap the path component for a symbolic link. The kernel re-resolves the path from scratch on the second call, and the privileged action lands on the attacker’s chosen target. Rust’s standard library makes this easy to get wrong. The ergonomic APIs you reach for first ( , , , ) all take a path and re-resolve it every time, rather than taking a file descriptor and operating relative to that. That’s fine for a normal program, but if you’re writing a privileged tool that needs to be secure against local attackers, you have to be careful. Here’s the bug, simplified from . Between step 1 and step 2, anyone with write access to the parent directory can plant as a symlink to, say, . Then follows the symlink and the privileged process happily overwrites with whatever happened to contain. The fix uses : The docs for say (emphasis mine): No file is allowed to exist at the target location, also no (dangling) symlink . In this way, if the call succeeds, the file returned is guaranteed to be new. A in Rust looks like a value, but remember that to the kernel it’s just a name. That name can point to different things from one syscall to the next. Anchor your operations on a file descriptor instead. only helps with that when you’re creating a new file. For everything else, open the parent directory once and work relative to that handle . If you act on the same path twice, assume it’s a TOCTOU (Time Of Check To Time Of Use) bug until you’ve proven otherwise. This is a close relative of TOCTOU. You want a directory with restrictive permissions, so you write something like this. For a brief moment, exists with the default permissions. Any other user on the system can it during that window. Once they have a file descriptor, the later doesn’t take it away from them. Reach for and so the file or directory is born with the permissions you want. The kernel will apply your on top, so set that explicitly too if you really care. The original check in was literally this: That comparison is bypassed by anything that resolves to but isn’t spelled . So , , , or a symlink that points to . Run and see it rip right past your check and lock down the whole system. Here’s the fix : resolves , , and symlinks into a real absolute path. That’s a lot better than string comparison. Oh and if you were wondering about this line: I think that’s just a fancy way of saying In the specific case of , this works because has no parent directory, so there’s nothing for an attacker to swap from underneath you. In the more general case of comparing two arbitrary paths for filesystem identity, however, you’d want to open both and compare their pairs, the way GNU coreutils does. (Think identity, not string equality.) By the way, my favorite bug in this group is CVE-2026-35363: It refused and but happily accepted and , then deleted the current directory while printing . 😅 Rust’s and are always UTF-8. That’s a great choice in 99% of all cases, but Unix paths, environment variables, arguments, and the inputs flowing through tools like , , and live in the messy world of bytes. Every time a Rust program bridges that gap, it has three options. The audit found bugs in both of the first two categories. Here’s an example. This is the original code, from . GNU works on binary files because it just shuffles bytes around. The uutils version replaced anything that wasn’t valid UTF-8 with , which silently corrupted the output. Here’s the fix: stay in bytes. forces a UTF-8 round-trip through . does not. It writes the raw bytes directly to . For Unix-flavored systems code, use and for filesystem paths, for environment variables, and or for stream contents. It’s tempting to round-trip them through for easier formatting, but that’s where the corruption creeps in. UTF-8 is a great default for application strings, but it’s absolutely, positively the wrong default for the raw byte stuff Unix tools work with. In a CLI, every , every , every slice index, every unchecked arithmetic operation, every is a potential denial of service if an attacker can shape the input. That’s because a unwinds the stack and aborts the process. If your tool is running in a cron job, a CI pipeline, or a shell script, that means the whole thing just stops working. Even worse, you could find yourself in a crash loop that paralyzes the entire system. A canonical case from the audit was ( CVE-2026-35348 ). The flag reads a NUL-separated list of filenames from a file, but the parser called on a UTF-8 conversion of each name: GNU treats filenames as raw bytes, the way the kernel does. The uutils version required UTF-8 and aborted the whole process on the first non-UTF-8 path: (I reproduced this against on macOS. The Python one-liner is there because most modern shells refuse to create a non-UTF-8 filename for you.) Your nightly cron job is dead and there goes your weekend. In code that processes untrusted input, treat every , , indexing, or cast as a CVE waiting to be filed. Use , , , , and surface a real error. Push back on the boundary of your application and let the caller deal with the fallout. A good lint baseline to catch this in CI: These are noisy in test code where panicking on bad data is exactly what you want. The cleanest way to scope them to non-test code is to put at the top of each crate root, or to gate on the individual modules. Closely related to the previous point, a few CVEs come from ignoring or losing error information. and returned the exit code of the last file processed instead of the worst one. So could fail on half the files and still exit . Your script thinks everything is fine. called on its call to mimic GNU’s behavior on . The intent was reasonable, but that same code ran for regular files too, so a full disk silently produced a half-written destination. The reason was that someone wanted to throw away a and reached for , , or . Here’s a very simple pattern to avoid that: Also, if you write to discard a , leave a comment that explains why this specific failure is safe to ignore. A surprising number of these CVEs aren’t “the code does something unsafe” but “the code does something different from GNU, and a shell script somewhere relied on the GNU behavior.” The clearest example is (CVE-2026-35369). GNU reads as “signal 1” and asks for a PID. uutils read it as “send the default signal to PID -1”, which on Linux means every process you can see . Yikes! A typo becomes a system-wide kill switch. If you reimplement a battle-tested tool, bug-for-bug compatibility on exit codes, error messages, edge cases, and option semantics is a security feature. (Hello, Hyrum’s Law – and obligatory XKCD 1172 !) Anywhere your behavior diverges from the original, somebody’s shell script is making a wrong decision. uutils now runs the upstream GNU coreutils test suite against itself in CI. That’s the right scale of defense for this class of bug. CVE-2026-35368 is the worst single bug in the audit. It’s local root code execution in . The bug is visible if you know what to look for (a followed by a function call that loads a dynamic library), but it’s the kind of thing that doesn’t jump out on a first read. Here’s the pattern, simplified from the utility. Huh. Looks innocent. The trap is that ends up loading shared libraries from the new root filesystem to resolve the username. An attacker who can plant a file in the chroot gets to run code as uid 0. GNU resolves the user before calling . Same fix here. Once you’re across, every library call might run the attacker’s code. And no, static compilation doesn’t help here, because goes through NSS, which s modules at runtime regardless of whether your binary is statically linked. You might have made it this far and thought “Wow, that’s a lot of bugs! Maybe Rust isn’t as safe as I thought?” That would be the wrong conclusion. Keep in mind that none of the following bad things happened: That means, even if the tools were (and probably still are) buggy, they never had a bug that could be exploited to read arbitrary memory. GNU coreutils has shipped CVEs in every single one of those categories. Take a peek at the last few years of the GNU file: …the list goes on and on. The Rust rewrite has shipped zero of these, over a comparable window of activity. 1 That’s most of what historically goes wrong in a C codebase. What’s left is, frankly, a more interesting class of bug. It lives at the boundary between our controlled Rust environment and the messy, chaotic outside world, where paths, bytes, strings, and syscalls are all tangled up in one eternal ball of sadness. That’s the new security boundary of modern systems code. 2 If you write systems code in Rust, treat this CVE list as a checklist. Grep your own codebase for , stray calls, discarded s, , and string comparisons against . I also wrote a companion post, titled Patterns for Defensive Programming in Rust . When I think of “ idiomatic Rust ”, correctness is not the first thing that comes to mind. After all, isn’t that the compiler’s job? Instead, I think of elegant iterator patterns , ergonomic method signatures, immutability , or clever use of expressions . But none of that matters if the code doesn’t do the right thing, and the compiler is far from perfect at enforcing correctness. That’s why we don’t only have idioms for writing more elegant code; we also have idioms for writing correct code. They are the distilled experience of a community that has learned, often painfully, which shapes of code survive contact with reality and which ones do not. Reality is rarely as tidy as the abstractions we would like to impose on it. The mark of robust systems, in any language, is the willingness to reflect that untidiness rather than paper over it. Rust gives us extraordinary tools to do so, and the compiler will hold a great deal for us. But the part it cannot hold, the boundary between our program and everything else, is still ours to get right. The type system can encode many things, but it cannot encode conditions outside of its control, such as the passage of time between two syscalls. Idiomatic Rust, then, is not just code that the borrow checker accepts or that leaves alone. It is code whose types, names, and control flow tell the truth about the system they run in. And that truth is sometimes ugly. It could mean using file descriptors instead of paths, instead of , instead of , and bug-for-bug compatibility over clean semantics. None of it is as pretty as the version you would write on a whiteboard. But it is more honest. Need Help Hardening Your Rust Codebase? Is your team shipping Rust into production and want to make sure you’re not falling into the same traps? I offer Rust consulting services, from code reviews and security-focused audits to training your team on the patterns that the compiler won’t enforce for you. Get in touch to learn more. To be fair to GNU: GNU coreutils is 40 years old and has had a very long time to surface and fix this class of bug. And we don’t know there are no memory-safety bugs in the Rust rewrite, only that the audit didn’t find any. Still, the difference is noticeable when comparing the same duration of development activity. ↩ It’s worth noting that the / TOCTOU class of bug is in some ways easier to avoid in C than in Rust. C code naturally reaches for an open file descriptor and the family of syscalls ( , , , ), and most creation syscalls take a argument directly. Rust’s high-level APIs abstract over the file descriptor and operate on values, which makes the path-based, re-resolving call the path of least resistance. The handle-based APIs exist on every Unix platform; Rust just doesn’t put them front and center. ↩ 🫩 Lossy conversion with silently rewrites invalid bytes to U+FFFD. That’s just fancy data corruption. 🫤 Strict conversion with or crashes or refuses to operate. 😚 Staying in bytes with or is what you should usually do. No buffer overflows. No use-after-free. No double-free. No data races on shared mutable state. No null-pointer dereferences. No uninitialized memory reads. buffer overflow on deep paths longer than (9.11, 2026) out-of-bounds read on trailing blanks (9.9, 2025) heap buffer overflow (9.9, 2025) writes a NUL byte past a heap buffer (9.8, 2025) 1-byte read before a heap buffer with a key offset (9.8, 2025) and crashes with SELinux but no xattr support (9.7, 2025) heap overwrite ( CVE-2024-0684 , 9.5, 2024) reads unallocated memory on malformed input (9.4, 2023) stack buffer overrun with many files and a high (9.0, 2021) To be fair to GNU: GNU coreutils is 40 years old and has had a very long time to surface and fix this class of bug. And we don’t know there are no memory-safety bugs in the Rust rewrite, only that the audit didn’t find any. Still, the difference is noticeable when comparing the same duration of development activity. ↩ It’s worth noting that the / TOCTOU class of bug is in some ways easier to avoid in C than in Rust. C code naturally reaches for an open file descriptor and the family of syscalls ( , , , ), and most creation syscalls take a argument directly. Rust’s high-level APIs abstract over the file descriptor and operate on values, which makes the path-based, re-resolving call the path of least resistance. The handle-based APIs exist on every Unix platform; Rust just doesn’t put them front and center. ↩

0 views
Corrode 1 months ago

Helsing

Jon Gjengset is one of the most recognizable names in the Rust community, the author of Rust for Rustaceans , a prolific live-streamer, and a long-time contributor to the Rust ecosystem. Today he works as a Principal Engineer at Helsing, a European defense company that has made Rust a foundational part of its engineering stack. Helsing builds safety-critical software for real-world defense applications, where correctness, performance, and reliability are non-negotiable. In this episode, Jon talks about what it means to build mission-critical systems in Rust, why Helsing bet on Rust from the start, and what lessons from his years of Rust education have shaped the way he writes and thinks about production code. 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 . Founded in 2021, Helsing is a European defence company building AI-enabled software for some of the most demanding environments imaginable. Helsing’s software runs where correctness is non-negotiable. That philosophy led them to Rust early on and they’ve leaned into it fully. From coordinate transforms to CRDT document stores to Protobuf package management, almost everything they build ends up being written in Rust. Jon holds a PhD from MIT’s PDOS group, where he built Noria, a high-performance streaming dataflow database, and later co-founded ReadySet to continue that work commercially. He then spent time building infrastructure at AWS, before joining Helsing as a Principal Engineer. Outside of his day job, he’s been teaching Rust to the world through his livestreams and writing for years, which makes him a rare combination: someone who thinks deeply about both how to use Rust and how to explain it. Helsing AI selected for Eurofighter upgrade - Helsing’s Eurofighter Project CA-1 Europa - Helsing’s Autonomous Uncrewed Combat Aerial Vehicle Rust in Python cryptography - Rust being used in a Python library Clippy Documentation: Adding Lints - How to add custom lints to (your own fork of) clippy anyhow’s .context() - Use it everywhere, it’s very very helpful eyre - A fork of with support for customizable, pluggable error report handlers miette - Fancy, diagnostic-rich error reporting for Rust with source snippets and labels buffrs - Helsing’s Cargo-inspired package manager for Protocol Buffers, written in Rust sguaba - Helsing’s Rust crate for type-safe coordinate system math, preventing unit and frame mix-ups at compile time Sguaba: Type-safe spatial math in Rust - Jon’s talk at Rust Amsterdam introducing sguaba and the type-system techniques behind it Apache Avro - A compact binary serialization format for streaming data, with a Rust implementation available via the crate pubgrub - A Rust implementation of the PubGrub version-solving algorithm, as used in Cargo and uv CRDTs - Conflict-free Replicated Data Types: data structures that can be merged across distributed nodes without conflicts ADR (Architecture Decision Record) - A lightweight way to document important architectural decisions and their context DSON: JSON CRDT using delta-mutations for document stores - The 2022 paper that was the basis for Helsing’s CRDT implementation dson - Helsing’s Rust implementation of DSON Jon’s Livestreams on YouTube - Deep-dive Rust coding sessions where Jon implements real-world libraries and systems from scratch WebAssembly with Rust - The official Rust and WebAssembly book, covering a cool technology and useful skills to have as a Rust developer Rust for Rustaceans - Jon’s book for intermediate Rust developers covering ownership, traits, async, and the finer points of the language CVE-2024-24576: Cargo/tar supply chain vulnerability - A security issue in the crate that affected Cargo’s package extraction Wikipedia: Defence in Depth - The security principle of using multiple independent layers of protection; Even with Rust you need multiple layers, there is no silver bullet SBOMs (Software Bill of Materials) - A machine-readable inventory of all components in a software artifact; Cargo’s lock files make this tractable for Rust projects Helsing: AI-assisted vetting of software packages - Make it more efficient to review dependencies you take in Bevy - A game engine built entirely in Rust, and a notable example of a large, complex Rust dependency Tauri - A Rust-powered framework for building lightweight desktop and mobile apps from a web frontend, an alternative to Electron Helsing Website Helsing Tech Blog Helsing on GitHub Helsing on LinkedIn Jon Gjengset’s Website Jon Gjengset on GitHub Jon Gjengset on YouTube Jon Gjengset on Bluesky Rust for Rustaceans

0 views
Corrode 1 months ago

Cloudsmith

Rust adoption can be loud, like when companies such as Microsoft, Meta, and Google announce their use of Rust in high-profile projects. But there are countless smaller teams quietly using Rust to solve real-world problems, sometimes even without noticing. This episode tells one such story. Cian and his team at Cloudsmith have been adopting Rust in their Python monolith not because they wanted to rewrite everything in Rust, but because Rust extensions were simply best-in-class for the specific performance problems they were trying to solve in their Django application. As they had these initial successes, they gained more confidence in Rust and started using it in more and more areas of their codebase. 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 . Made with love in Belfast and trusted around the world. Cloudsmith is the fully-managed solution for controlling, securing, and distributing software artifacts. They analyze every package, container, and ML model in an organization’s supply chain, allow blocking bad packages before they reach developers, and build an ironclad chain of custody. Cian is a Service Reliability Engineer located in Dublin, Ireland. He has been working with Rust for 10 years and has a history of helping companies build reliable and efficient software. He has a BA in Computer Programming from Dublin City University. Lee Skillen’s blog - The blog of Lee Skillen, Cloudsmith’s co-founder and CTO Django - Python on Rails Django Mixins - Great for scaling up, not great for long-term maintenance SBOM - Software Bill of Materials Microservice vs Monolith - Martin Fowler’s canonical explanation Jaeger - “Debugger” for microservices PyO3 - Rust-to-Python and Python-to-Rust FFI crate orjson - Pretty fast JSON handling in Python using Rust drf-orjson-renderer - Simple orjson wrapper for Django REST Framework Rust in Python cryptography - Parsing complex data formats is just safer in Rust! jsonschema-py - jsonschema in Python with Rust, mentioned in the PyO3 docs WSGI - Python’s standard for HTTP server interfaces uWSGI - A application server providing a WSGI interface rustimport - Simply import Rust files as modules in Python, great for prototyping granian - WSGI application server written in Rust with tokio and hyper hyper - HTTP parsing and serialization library for Rust HAProxy - Feature rich reverse proxy with good request queue support nginx - Very common reverse proxy with very nice and readable config locust - Fantastic load-test tool with configuration in Python goose - Locust, but in Rust Podman - Daemonless container engine Docker - Container platform buildx - Docker CLI plugin for extended build capabilities with BuildKit OrbStack - Faster Docker for Desktop alternative Rust in Production: curl with Daniel Stenberg - Talking about hyper’s strictness being at odds with curl’s permissive design axum - Ergonomic and modular web framework for Rust rocket - Web framework for Rust Cloudsmith Website Cian Butler’s Website Cian’s E-Mail

0 views
Corrode 4 months ago

Gama Space

Space exploration demands software that is reliable, efficient, and able to operate in the harshest environments imaginable. When a spacecraft deploys a solar sail millions of kilometers from Earth, there’s no room for memory bugs, race conditions, or software failures. This is where Rust’s robustness guarantees become mission-critical. In this episode, we speak with Sebastian Scholz, an engineer at Gama Space, a French company pioneering solar sail and drag sail technology for spacecraft propulsion and deorbiting. We explore how Rust is being used in aerospace applications, the unique challenges of developing software for space systems, and what it takes to build reliable embedded systems that operate beyond Earth’s atmosphere. 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 . Gama Space is a French aerospace company founded in 2020 and headquartered in Ivry-sur-Seine, France. The company develops space propulsion and orbital technologies with a mission to keep space accessible. Their two main product lines are solar sails for deep space exploration using the sun’s infinite energy, and drag sails—the most effective way to deorbit satellites and combat space debris. After just two years of R&D, Gama successfully launched their satellite on a SpaceX Falcon 9. The Gama Alpha mission is a 6U cubesat weighing just 11 kilograms that deploys a large 73.3m² sail. With 48 employees, Gama is at the forefront of making space exploration more sustainable and accessible. Sebastian Scholz is an engineer at Gama Space, where he works on developing software systems for spacecraft propulsion technology. His work involves building reliable, safety-critical embedded systems that must operate flawlessly in the extreme conditions of space. Sebastian brings expertise in systems programming and embedded development to one of the most demanding environments for software engineering. GAMA-ALPHA - The demonstration satellite launched in January 2023 Ada - Safety-focused programming language used in aerospace probe-rs - Embedded debugging toolkit for Rust hyper - Fast and correct HTTP implementation for Rust Flutter - Google’s UI toolkit for cross-platform development UART - Very common low level communication protocol Hamming Codes - Error correction used to correct bit flips Rexus/Bexus - European project for sub-orbital experiments by students Embassy - The EMBedded ASsYnchronous framework CSP - The Cubesat Space Protocol std::num::NonZero - A number in Rust that can’t be 0 std::ffi::CString - A null-byte terminated String Rust in Production: KSAT - Our episode with Vegard about using Rust for Ground Station operations Rust in Production: Oxide - Our episode with Steve, mentioning Hubris Hubris - Oxide’s embedded operating system ZeroCopy - Transmute data in-place without allocations std::mem::transmute - Unsafe function to treat a memory section as a different type than before Gama Space Website Gama Space on LinkedIn Gama Space on Crunchbase

0 views
Corrode 5 months ago

2025 Holiday Special

As we close the chapter on 2025 and celebrate our second year of ‘Rust in Production’, it’s time to reflect on the highlights of the 17 episodes since our last holiday special. We looked at Rust from all angles, from cloud infrastructure to embedded systems, and from robotics to satellite technology. One thing that all these stories have in common is the passion and dedication of the Rust community to build faster, safer, and more reliable software. In this special episode, we look back at some of the memorable moments from the past year and celebrate Rust’s achievements. This goes beyond the case studies we’ve covered; it’s about the Rust community as a whole and the state of the Rust ecosystem at the end of 2025. 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 . Code.Talks Talk - Matthias’ presentation on Rust case studies Stack Overflow Developer Survey 2025 - Rust as most admired language since 1.0 release Brave with Anton Lazarev (S03E07) - Rust as the go-to language Volvo with Julius Gustavsson (S03E08) - Empowering engineers Astral with Charlie Marsh (S04E03) - Welcoming community leads to huge impact Scythe with Andrew Tinka (S05E02) - Confidence in what you build Rust4Linux CVE - The first CVE in Rust for Linux Greg KH post - Context on kernel CVE statistics curl with Daniel Stenberg (S02E01) - Bug reports every three hours, code constantly changes curl statistics - How old code gets rewritten all the time Tembo with Adam Hendel (S04E05) - Software is never done Redis CVE-2025-49844 - Remote code execution vulnerability from use-after-free Canonical with John Seager (S05E05) - Ubuntu is optimistic about Rust Rust in Android - Memory safety vulnerabilities below 20% Android statistics - 3.9 billion active devices worldwide Roc with Richard Feldman (S05E04) - Focus on the end user Svix with Tom Hacohen (S04E02) - Love it, but compile times… Prime Video with Alexandru Ene (S05E01) - Build times need to improve crates.io - 200 billion crate downloads and 200k published crates Cloudflare with Kevin Guthrie and Edward Wang (S05E03) - Ecosystem is fantastic; thanks to all maintainers Rust Conferences 2026 - Complete list of upcoming Rust conferences CodeCrafters Course - Build your own HTTP server in Rust Rust Project Goals - November update on 41 active project goals cargo-script RFC - Run Rust scripts without full Cargo projects Better pin ergonomics RFC - Improving async Rust ergonomics KSAT with Vegard Sandengen (S04E07) - Make async better 1Password with Andrew Burkhart (S04E06) - Make it easier to learn Rust Rust Book by Brown University - Interactive learning resource for Rust Clippy lints - All available linter rules for Rust C++ and Rust interop - Safer language interoperability initiative Microsoft with Victor Ciura (S04E01) - C++ doesn’t have to die for Rust to succeed BorrowSanitizer initiative - LLVM instrumentation for detecting aliasing violations Polonius - Next-generation borrow checker Rust with Niko Matsakis (S04E04) - Be excellent to each other (Bill & Ted reference)

0 views
Corrode 5 months ago

Rust for Linux

Bringing Rust into the Linux kernel is one of the most ambitious modernization efforts in open source history. The Linux kernel, with its decades of C code and deeply ingrained development practices, is now opening its doors to a memory-safe language. It’s the first time in over 30 years that a new programming language has been officially adopted for kernel development. But the journey is far from straightforward. In this episode, we speak with Danilo Krummrich, Linux kernel maintainer and Rust for Linux core team member, about the groundbreaking work of integrating Rust into the Linux kernel. Among other things, we talk about the Nova GPU driver, a Rust-based successor to Nouveau for NVIDIA graphics cards, and discuss the technical challenges and cultural shifts required for large-scale Rust adoption in the kernel as well as the future of the Rust4Linux project. 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 for Linux is a project aimed at bringing the Rust programming language into the Linux kernel. Started to improve memory safety and reduce vulnerabilities in kernel code, the project has been gradually building the infrastructure, abstractions, and tooling necessary for Rust to coexist with the kernel’s existing C codebase. Danilo Krummrich is a software engineer at Red Hat and a core contributor to the Rust for Linux project. In January 2025, he was officially added as a reviewer to the RUST entry in the kernel’s MAINTAINERS file, recognizing his expertise in developing Rust abstractions and APIs for kernel development. Danilo maintains the and branches and is the primary developer of the Nova GPU driver, a fully Rust-based driver for modern NVIDIA GPUs. He is also a maintainer of RUST [ALLOC] and several DRM-related kernel subsystems. AOSP - The Android Open Source Project Kernel Mailing Lists - Where the Linux development happens Miguel Ojeda - Rust4Linux maintainer Wedson Almeida Filho - Retired Rust4Linux maintainer noveau driver - The old driver for NVIDIA GPUs Vulkan - A low level graphics API Mesa - Vulkan and OpenGL implementation for Linux vtable - Indirect function call, a source of headaches in nouveau DRM - Direct Rendering Manager, Linux subsystem for all things graphics Monolithic Kernel - Linux’ kernel architecture The Typestate Pattern in Rust - A very nice way to model state machines in Rust pinned-init - The userspace crate for pin-init rustfmt - Free up space in your brain by not thinking about formatting kunit - Unit testing framework for the kernel Rust core crate - The only part of the Rust Standard Library used in the Linux kernel Alexandre Courbot - NVIDIA employed co-maintainer of nova-core Greg Kroah-Hartman - Linux Foundation fellow and major Linux contributor Dave Airlie - Maintainer of the DRM tree vim - not even neovim mutt - classic terminal e-mail client aerc - a pretty good terminal e-mail client Rust4Linux Zulip - The best entry point for the Rust4Linux community Rust for Linux GitHub Danilo Krummrich on GitHub Danilo Krummrich on LinkedIn

0 views
Corrode 6 months ago

Canonical

What does it take to rewrite the foundational components of one of the world’s most popular Linux distributions? Ubuntu serves over 12 million daily desktop users alone, and the systems that power it, from sudo to core utilities, have been running for decades with what Jon Seager, VP of Engineering for Ubuntu at Canonical, calls “shaky underpinnings.” In this episode, we talk to Jon about the bold decision to “oxidize” Ubuntu’s foundation. We explore why they’re rewriting critical components like sudo in Rust, how they’re managing the immense risk of changing software that millions depend on daily, and what it means to modernize a 20-year-old operating system without breaking the internet. 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 . Canonical is the company behind Ubuntu, one of the most widely-used Linux distributions in the world. From personal desktops to cloud infrastructure, Ubuntu powers millions of systems globally. Canonical’s mission is to make open source software available to people everywhere, and they’re now pioneering the adoption of Rust in foundational system components to improve security and reliability for the next generation of computing. Jon Seager is VP Engineering for Ubuntu at Canonical, where he oversees the Ubuntu Desktop, Server, and Foundations teams. Appointed to this role in January 2025, Jon is driving Ubuntu’s modernization strategy with a focus on Communication, Automation, Process, and Modernisation. His vision includes adopting memory-safe languages like Rust for critical infrastructure components. Before this role, Jon spent three years as VP Engineering building Juju and Canonical’s catalog of charms. He’s passionate about making Ubuntu ready for the next 20 years of computing. Juju - Jon’s previous focus, a cloud orchestration tool GNU coretuils - The widest used implementation of commands like ls, rm, cp, and more uutils coreutils - coreutils implementation in Rust sudo-rs - For your Rust based sandwiches needs LTS - Long Term Support, a release model popularized by Ubuntu coreutils-from-uutils - List of symbolic links used for coreutils on Ubuntu, some still point to the GNU implementation man: sudo -E - Example of a feature that sudo-rs does not support SIMD - Single instruction, multiple data rust-coreutils - The Ubuntu package with all it’s supported CPU platforms listed fastcat - Matthias’ blogpost about his faster version of systemd-run0 - Alternative approach to sudo from the systemd project AppArmor - The Linux Security Module used in Ubuntu PAM - The Pluggable Authentication Modules, which handles all system authentication in Linux SSSD - Enables LDAP user profiles on Linux machines ntpd-rs - Timesynchronization daemon written in Rust which may land in Ubuntu 26.04 Trifecta Tech Foundation - Foundation supporting sudo-rs development Sequioa PGP - OpenPGP tools written in Rust Mir - Canonicals wayland compositor library, uses some Rust Anbox Cloud - Canonical’s Android streaming platform, includes Rust components Simon Fels - Original creator of Anbox and Anbox Cloud team lead at Canonical LXD - Container and VM hypervisor dqlite - SQLite with a replication layer for distributed use cases, potentially being rewritten in Rust Rust for Linux - Project to add Rust support to the Linux kernel Nova GPU Driver - New Linux OSS driver for NVIDIA GPUs written in Rust Ubuntu Asahi - Community project for Ubuntu on Apple Silicon debian-devel: Hard Rust requirements from May onward - Parts of apt are being rewritten in Rust (announced a month after the recording of this episode) Go Standard Library - Providing things like network protocols, cryptographic algorithms, and even tools to handle image formats Python Standard Library - The origin of “batteries included” The Rust Standard Library - Basic types, collections, filesystem access, threads, processes, synchronisation, and not much more clap - Superstar library for CLI option parsing serde - Famous high-level serilization and deserialization interface crate Jon Seager’s Website Jon’s Blog: Engineering Ubuntu For The Next 20 Years Canonical Blog Ubuntu Blog Canonical Careers: Engineering - Apply your Rust skills in the Linux ecosystem

0 views
Corrode 7 months ago

Patterns for Defensive Programming in Rust

I have a hobby. Whenever I see the comment in code, I try to find out the exact conditions under which it could happen. And in 90% of cases, I find a way to do just that. More often than not, the developer just hasn’t considered all edge cases or future code changes. In fact, the reason why I like this comment so much is that it often marks the exact spot where strong guarantees fall apart. Often, violating implicit invariants that aren’t enforced by the compiler are the root cause. Yes, the compiler prevents memory safety issues, and the standard library is best-in-class. But even the standard library has its warts and bugs in business logic can still happen. All we can work with are hard-learned patterns to write more defensive Rust code, learned throughout years of shipping Rust code to production. I’m not talking about design patterns here, but rather small idioms, which are rarely documented, but make a big difference in the overall code quality. Here’s some innocent-looking code: This code works for now, but what if you refactor it and forget to keep the length check? That’s our first implicit invariant that’s not enforced by the compiler. The problem is that indexing into a vector is decoupled from checking its length: these are two separate operations, which can be changed independently without the compiler ringing the alarm. If we use slice pattern matching, we’ll only get access to the element if the arm is executed. Note how this automatically uncovered one more edge case: what if the list is empty? We hadn’t considered this case before. The compiler-enforced pattern matching forces us to think about all possible states! This is a common pattern throughout robust Rust code, the attempt to put the compiler in charge of enforcing invariants. When initializing an object with many fields, it’s tempting to use to fill in the rest. In practice, this is a common source of bugs. You might forget to explicitly set a new field later when you add it to the struct (thus using the default value instead, which might not be what you want), or you might not be aware of all the fields that are being set to default values. Instead of this: Yes, it’s slightly more verbose, but what you gain is that the compiler will force you to handle all fields explicitly. Now when you add a new field to , the compiler will remind you to set it here as well and reflect on which value makes sense. Let’s say you’re building a pizza ordering system and have an order type like this: For your order tracking system, you want to compare orders based on what’s actually on the pizza - the , , and . The timestamp shouldn’t affect whether two orders are considered the same. Here’s the problem with the obvious approach: Now imagine your team adds a field for customization options: Your implementation still compiles, but is it correct? Should be part of the equality check? Probably yes - a pizza with extra cheese is a different order! But you’ll never know because the compiler won’t remind you to think about it. Here’s the defensive approach using destructuring: Now when someone adds the field, this code won’t compile anymore. The compiler forces you to decide: should be included in the comparison or explicitly ignored with ? This pattern works for any trait implementation where you need to handle struct fields: , , , etc. It’s especially valuable in codebases where structs evolve frequently as requirements change. Sometimes there’s no conversion that will work 100% of the time. That’s fine. When that’s the case, resist the temptation to offer a implementation out of habit; use instead. Here’s an example of in disguise: The is a hint that this conversion can fail in some way. We set a default value instead, but is it really the right thing to do for all callers? This should be a implementation instead, making the fallible nature explicit. We fail fast instead of continuing with a potentially flawed business logic. It’s tempting to use in combination with a catch-all pattern like , but this can haunt you later. The problem is that you might forget to handle a new case that was added later. Instead of: By spelling out all variants explicitly, the compiler will warn you when a new variant is added, forcing you to handle it. Another case of putting the compiler to work. If the code for two variants is the same, you can group them: Using as a placeholder for unused variables can lead to confusion. For example, you might get confused about which variable was skipped. That’s especially true for boolean flags: In the above example, it’s not clear which variables were skipped and why. Better to use descriptive names for the variables that are not used: Even if you don’t use the variables, it’s clear what they represent and the code becomes more readable and easier to review without inline type hints. If you only want your data to be mutable temporarily, make that explicit. This pattern is often called “temporary mutability” and helps prevent accidental modifications after initialization. See the Rust unofficial patterns book for more details. Let’s say you had a simple type like the following: Now you want to make invalid states unrepresentable. One pattern is to return a from the constructor. But nothing stops someone from creating an instance of directly: This should not be possible! One way to prevent this is to make the struct non-exhaustive: Now the struct cannot be instantiated directly outside of the module. However, what about the module itself? One way to prevent this is to add a hidden field: Now the struct cannot be instantiated directly even inside the module. You have to go through the constructor, which enforces the validation logic. The attribute is often neglected. That’s sad, because it’s such a simple yet powerful mechanism to prevent callers from accidentally ignoring important return values. Now if someone creates a but forgets to use it, the compiler will warn them: This is especially useful for guard types that need to be held for their lifetime and results from operations that must be checked. The standard library uses this extensively. For example, is marked with , which is why you get warnings if you don’t handle errors. Boolean parameters make code hard to read at the call site and are error-prone. We all know the scenario where we’re sure this will be the last boolean parameter we’ll ever add to a function. It’s impossible to understand what this code does without looking at the function signature. Even worse, it’s easy to accidentally swap the boolean values. Instead, use enums to make the intent explicit: This is much more readable and the compiler will catch mistakes if you pass the wrong enum type. You will notice that the enum variants can be more descriptive than just or . And more often than not, there are more than two meaningful options; especially for programs which grow over time. For functions with many options, you can configure them using a parameter struct: This approach scales much better as your function evolves. Adding new parameters doesn’t break existing call sites, and you can easily add defaults or make certain fields optional. The preset methods also document common use cases and make it easy to use the right configuration for different scenarios. Rust is often criticized for not having named parameters, but using a parameter struct is arguably even better for larger functions with many options. Many of these patterns can be enforced automatically using Clippy lints. Here are the most relevant ones: You can enable these in your project by adding them to your or at the top of your crate, e.g. Defensive programming in Rust is about leveraging the type system and compiler to catch bugs before they happen. By following these patterns, you can: It’s a skill that doesn’t come naturally and it’s not covered in most Rust books, but knowing these patterns can make the difference between code that works but is brittle, and code that is robust and maintainable for years to come. Remember: if you find yourself writing , take a step back and ask how the compiler could enforce that invariant for you instead. The best bug is the one that never compiles in the first place. Make implicit invariants explicit and compiler-checked Future-proof your code against refactoring mistakes Reduce the surface area for bugs

0 views
Corrode 8 months ago

Prime Video

Are you one of over 240 million subscribers of Amazon’s Prime Video service? If so, you might be surprised to learn that much of the infrastructure behind Prime Video is built using Rust. They use a single codebase for media players, game consoles, and tablets. In this episode, we sit down with Alexandru Ene, a Principal Engineer at Amazon, to discuss how Rust is used at Prime Video, the challenges they face in building a global streaming service, and the benefits of using Rust for their systems. 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 . Prime Video is a streaming service offered by Amazon that provides a wide range of movies, TV shows, and original content to its subscribers. With over 240 million subscribers worldwide, Prime Video is one of the largest streaming platforms in the world. In addition to its vast content library, Prime Video also offers features such as offline viewing, 4K streaming, and support for multiple devices. On the backend, Prime Video relies on a variety of technologies to deliver its content, including Rust, which is used for building high-performance and reliable systems that can handle the demands of a global audience. Alexandru worked on the transition of Prime Video’s user interface from JavaScript to Rust. He has been with Amazon for over 8 years and previously worked at companies like Ubisoft and EA. He has a background in computer science and is an active open source maintainer. Alexandru lives in London. Ferris Makes Emulators Ep.001 - The Journey Begins - First episode of a famous series where Jake Taylor wrote a Nintendo 64 emulator in Rust from scratch CMake - Very common build system used in C++ applications Conan - C++ Package Manager community project C++ Smart Pointers - Still a footgun Herb Sutter: The Free Lunch Is Over - The seminal 2005 paper that highlights the importance of concurrency, well past C++’s mainstream adoption Rust in Production: cURL - Baseline library used everywhere, written in C, but performant and safe Prime Video Platforms - One app runs on all of these WebAssembly (WASM) - Enabling Rust code with good performance that you can still download and run like JavaScript, avoiding the need for firmware updates on some devices Entity Component System - Used in the UI Rust code for pages in the app Bevy - Game engine written in Rust Leptos - UI framework that makes reactive programming in Rust easier tokio - The de facto standard async runtime for Rust SIMD - A nice feature set some CPUs support WebAssembly Micro Runtime - A tiny WASM runtime well suited for IoT platforms WebAssembly Working Group Amazon Prime Video Rust & WASM for UI: Faster Prime Video on ANY Device - Alexandru Ene, QCon San Francisco 2024 Alexandru Ene on LinkedIn Alexandru’s Blog Alexandru Ene on GitHub

0 views
Corrode 8 months ago

Be Simple

The phone buzzes at 3 AM. You roll out of bed, open your laptop, and see this in the logs: You open the codebase and find this: 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 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 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: You find this code: 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. 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 be 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 stumbling over their own shoelaces. They use all the advanced features at their disposal without thinking much about maintainability. 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 them. That is the cruft we all should challenge. And simplicity also has other benefits: 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. 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 wanted to parse a CSV file. Turns out there never once was a need to parse anything other than CSV files. Of course, the original author likely had good reasons for their design. Maybe prior experience might have suggested that more formats would be needed in the future? Or the team wanted to simplify testing by mocking the data format and ended up with a set of abstractions that turned out to be limiting? And shouldn’t the problem have been discovered during code review? These are all valid points! But code reviews also take effort, and it’s easy to miss the forest for the trees. A less experienced Rust developer might not have the experience to evaluate the situation properly and saying “I don’t get it” also takes courage. And even if the code review had caught the complexity, it might have consumed a lot of energy to convince the original author to simplify the code or to change the design because of deadlines or sunk cost fallacies. It’s easy to judge past decisions with hindsight; that’s not my intention. Quite the opposite, actually: a series of individually perfectly reasonable decisions can lead to an overly complex, unmaintainable system. And taken in isolation, each small bit of complexity looks harmless. But complexity quickly compounds. One lesson here is that the path to complexity is paved with good intentions. Another is that senior engineers should pair with junior ones more often to avoid knowledge silos. 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 very real consequences. At some point, complexity will slow you down because it causes cognitive load. And cognitive load matters a lot because our mental capacity is finite. 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. 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 switch companies 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 a beginner’s eyes. 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. The classic example is “we might need to switch databases in the future” leading to over-abstracted data access layers that nobody asked for. 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 also 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. Use generics sparingly. 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 or a or something else as an input to your functions and why. That’s quite simple and doesn’t allocate. But what if the caller wants to pass a String? We take ownership of the input. But hold on, what if we don’t need ownership and we want to support both? That works. But do you see how the complexity goes up? Behind the scenes, it monomorphizes the function for each type that implements . That means that if we pass a and a , we get two copies of that function. That means longer compile times and larger binaries. Wait, what if we need to return something that references the input and we need the result to live as long as the input? Oh wait, we might need to send this across threads: See how we went from a simple parameter to this monstrosity? Each step seemed “reasonable,” but we’ve created a nightmare that nobody wants to read or debug. The problem is so simple, so how did that complexity creep in? We were trying to be clever! We were trying to improve the function 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 . Or, we could return an iterator instead: 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. 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. The idea is pretty simple and can fit on a napkin: Here’s an ad-hoc version of this algorithm in Rust: It’s not too far off from the description. Yes, my simple implementation only supports right now and for any real-world use case, you should use the built-in sort algorithm , which is 20x faster than the above version in my benchmarks, but my point is that simple code packs a punch. On my machine, this implementation sorts 100k numbers in 1 millisecond. 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. We should leverage that more often. 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. 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 ) 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: Yes, you could make it generic over or support multiple alphabets: …and you might even offer a builder pattern for maximum flexibility: But what most users care about is how to get an encoded string: You could call the function above or to make it clear that it’s a simplified version of a more generic algorithm. 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. It’s fine to offer additional functionality, but don’t make the easy thing hard in the process. Jerry Seinfeld had two writing modes: creating mode and editing mode . 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 naïve 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! 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. The result is that the code performs the same or becomes slower or is otherwise less ideal than the previous version. Measure twice, cut once. 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: 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 might 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! It’s fine to make 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. Rust is super fast, so you can literally commit all the performance crimes you like. Clone liberally, iterate over the same data structure multiple times, use a vector where a hashmap would be better. It simply doesn’t matter. Hardware is fast and cheap, so put it to work. Despite this reality, there’s a noticeable tendency among Rust developers to add significant complexity or extra code to avoid any and all allocations in code that isn’t even performance-critical. I think that’s because Rust is very anal about memory management, which makes developers hyper-aware of these costs. This awareness can get in the way of simplicity. Sometimes using or is not worth optimizing away if it keeps the code simple. If you’d like to learn more, I gave a talk on that topic titled The Four Horsemen of Bad Rust Code . All of the above doesn’t mean you should not learn about the good 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 and keep moving. Use all the features you need and none you don’t. Crucially, writing simple software does not mean implementing “good-enough-for-now” software. That approach surely will lead to technical debt and maintenance nightmares further down the line as workarounds get stacked on top of each other. No, simple software can still be well-designed software, but it keeps the current requirements in mind. The goal is not to take shortcuts. Quite the contrary: the goal is to avoid unnecessary complexity, which often is the root cause for having to take shortcuts later on when bad systems design prevents a cleaner solution. In code reviews , I pay extra attention to ensuring we don’t cut corners to avoid doing the necessary design work. 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. The result of that process is what you’ll often see in popular libraries. 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 original sin of a smart engineer is to optimize a thing that should not exist in the first place. Cross that bridge when you get there. 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. 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. ↩ For reference, see “The Power of 10 Rules” by Gerard J. Holzmann of the NASA/JPL Laboratory for Reliable Software. ↩ 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. ↩ 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. ↩

0 views
Corrode 10 months ago

Season 4 - Finale

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!

0 views
Corrode 10 months ago

KSAT

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

0 views
Corrode 11 months ago

1Password

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

0 views
Corrode 11 months ago

Tembo

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.

0 views
Corrode 11 months ago

Rust For Foundational Software

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 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 “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 they don’t see how Rust could help them. 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. In practice, my clients use Rust for a much broader range of software , not just kernel drivers and embedded code! 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 hardcore systems person. 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 of them don’t even have any C++ in production to begin with. They also don’t need to work on the hardware-software interface or spend their time in low-level code. What all clients 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 after taking a leap of faith and using it for an extensive period of time. In Rust, 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 a breeze. 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 to 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 tackle 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 many other languages also offer) 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 suit: Amazon, Google, Microsoft, Meta, Discord, Cloudflare, and many more. These organizations build platforms and Rust, a tool for professional programmers, developed by world experts over more than a decade of hard work, is best equipped to fit the bill. 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 often measured in decades. Stability plays a big role in that. The fact that Rust has stable editions and a language specification is crucial for industry adoption. But Rust is more than 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 maintain key libraries and tools in their free time. There is more to be said about supply-chain security and sustainability in a future post. 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; that’s critical infrastructure. And Rust allows us to do so with great ergonomics. Rust inherited the pragmatism from C++ and the 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! Conveniently, it also has a ‘C’ and an ‘R’ in the name, which bridges both languages. ↩

0 views
Corrode 1 years ago

Rust

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.

0 views
Corrode 1 years ago

Sharp Edges In The Rust Standard Library

The Rust standard library , affectionately called , 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 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. 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 it. For more details on the topic, check out matklad’s article: “Join Your Threads” . In fact, there was a proposal to make be , 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 is the easiest way of spawning threads, but not the best one for casual use. Manually calling is a chore and easy to forget, which makes a potential footgun. For new code, I recommend using 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- 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. 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 . 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 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 . Even the documentation of itself agrees: NOTE: It is almost always better to use or 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, is still a nightly-only feature 2 : Even if you wanted a linked list, it probably would not be : 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 as well, but I leave it at that. 3 does a decent job of abstracting away the underlying file system. One thing I always disliked was that returns a instead of a . I mentioned in my ‘Pitfalls of Safe Rust’ article that joining a relative path with an absolute path results in the absolute path being returned. 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 . That’s always a fun dance: These operations must be repeated everywhere. It makes path manipulation every so slightly annoying. There are a few more issues with paths in Rust: Of course, for everyday use, is perfectly okay, but if path handling is a core part of your application, you might want to consider using an external crate instead. 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 is platform dependent, which causes some headaches . Same for , 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 : The documentation does not specify the clock’s accuracy or how it handles leap seconds, except to note that 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 works well in combination with the rest of the standard library, such as for : …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: 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 For a more in-depth discussion on why the Linux kernel uses linked lists, see this article . ↩ Perhaps this is a little surprising if you mostly use vectors, but removing an element from a list is an operation. ↩ “Hold on, what’s wrong with BTreeMap?” you might ask. Okay, as you know, Rust has two map implementations in the standard library: , which guarantees insertion ordering, while is unordered, but more commonly used. For a long time, a “performance trick” was to use if you needed a faster hash map implementation. Since then, the performance of has improved significantly. One reason is that the implementation of has changed to use a “siphash” algorithm and is now based on Google’s SwissTable . The combination of these changes has made much more performant than before, so there is no good reason to use anymore, other than the ordering guarantee. One important distinction to note: iteration order over a is random, while ’s iteration order is always sorted by the key’s implementation (not by insertion order). This makes useful when you need to iterate over keys in a sorted manner. If you’re aggregating data in a but need a sorted list, you’ll need to collect into a vector and sort it manually. In contrast, gives you sorted iteration for free. So while is better for random access operations, 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 and . Here are the key takeaways: 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 out there in the ecosystem. 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, has been deprecated for years and is now not getting removed, but instead will be fixed with a bugfix release and un-deprecated . ↩

0 views
Corrode 1 years ago

C++ to Rust Cheat-Sheet

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.

0 views
Corrode 1 years ago

Astral

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: – an extremely fast Python package and project manager that supersedes all previous attempts; For example, it is 10x-100x faster than pip.

0 views