The conventional story about Rust goes like this: the borrow checker is brutal at first, but once you internalize ownership, the language rewards you. Rough start, smooth finish. The Rust team’s recent challenge survey complicates that story considerably.
What they found, across cohorts ranging from beginners to experienced engineers adopting Rust in production, is that the challenges don’t go away. They change shape. Beginners fight ownership semantics. Intermediate developers wrestle with lifetime annotations in complex generic code. Senior engineers and teams hit an entirely different wall: async runtimes that don’t compose cleanly, safety-critical certification paths that barely exist, embedded ecosystems that cover 80% of what you need and stall on the remaining 20%. One challenge traded for another at every level.
This matters because the way the Rust community frames onboarding and tooling investment follows the conventional story. If the assumption is that expertise solves the problem, the energy goes into better error messages, rustlings, and “the book.” Those are good investments, but they don’t address what keeps experienced teams frustrated.
Compilation Times as the Universal Tax
One finding holds regardless of experience level or domain: compilation times. The survey found that every cohort, from novices to experts, from embedded developers to web developers, cited compile times as a significant productivity barrier. One interviewee put it plainly: Java takes around 100 milliseconds; Rust takes considerably longer.
The underlying reasons are structural. Rust’s compilation model performs whole-crate monomorphization and relies heavily on LLVM for code generation and optimization, both of which are expensive. The borrow checker, MIR (Mid-level Intermediate Representation) passes, and LLVM’s optimization pipeline run serially per compilation unit in the common case. A large crate like tokio or serde can take tens of seconds from scratch on modern hardware.
The community has developed workarounds. cargo check runs only the frontend passes and produces no binary, making it much faster for iteration. The Cranelift backend, accessible via cargo build -Z codegen-backend=cranelift, skips many of LLVM’s expensive optimizations in exchange for faster debug builds. The parallel frontend, stabilized incrementally over recent releases, lets the compiler process multiple crates and items simultaneously.
These help. They don’t solve it. Incremental compilation in Rust has a long history of correctness issues that caused it to be disabled or limited by default at various points. The problem isn’t just throughput; it’s that the dependency graph for a typical Rust project with async support, serialization, and error handling often pulls in dozens of procedural macro crates that each run compilation phases of their own.
For a team working on a microservice with a 45-second clean build, cargo check in 8 seconds is a meaningful improvement. For a team doing embedded development with frequent reflash cycles, or a team working on a large monorepo, the arithmetic is worse and the workarounds are less complete.
The Async Story Is Better Than It Was, and Still Complicated
Async Rust has matured substantially since the initial stabilization of async/await in Rust 1.39 in November 2019. The most significant long-standing gap, the inability to use async fn in traits without the async-trait proc macro, was finally closed in Rust 1.75 (December 2023). That removed one of the most common sources of friction for developers building trait-based abstractions over async code.
But the problems the survey surfaced for network and backend developers go beyond trait syntax. The fundamental issue is that Rust has no standard async runtime. Tokio is dominant in practice, but async-std, smol, and embassy (in embedded contexts) exist alongside it, and they don’t interoperate. A library written against Tokio’s spawn or TcpStream doesn’t run on async-std without changes. The ecosystem has converged on tokio for most purposes, but the fragmentation has left scars in API design and documentation.
Pin<P> and the Unpin marker trait remain a genuine conceptual barrier. When you write async Rust at the level of futures combinators or custom executor logic, you encounter the self-referential struct problem that Pin was designed to solve. The mechanics are sound, but the type system surface area is significant, and errors involving Pin and Unpin are among the least intuitive in the language. Async closures, which require different handling than sync closures in the type system, are another area where the edges still show.
Async drop, the ability to run async cleanup logic when a value goes out of scope, is not yet stabilized. This means async destructors require manual workarounds, typically explicit .close().await calls or wrapping resources in types that enforce cleanup at the call site. For database connections, file handles, and network sockets in async code, this is a real limitation.
Safety-Critical Certification: The Gap Between Capability and Qualification
Rust’s memory safety guarantees make it technically compelling for safety-critical systems. In practice, using Rust in domains like automotive software (ISO 26262), aviation (DO-178C), or industrial control (IEC 61508) requires a qualified toolchain, and that’s where things get harder.
Ferrocene, developed by Ferrous Systems and subsequently supported by AdaCore, is the primary qualified Rust toolchain. It achieved qualification for ISO 26262 ASIL D and IEC 61508 SIL 4 and has been accepted as a component in automotive toolchains. That’s meaningful progress. But the qualification scope is narrow: specific Rust language features, specific targets, specific compiler versions. Teams working outside that scope, or targeting aviation or medical device certification frameworks that Ferrocene doesn’t yet cover, are largely on their own.
For years, Rust lacked a formal language specification entirely. The reference served as informal documentation, and the compiler behavior was the de facto spec. The Ferrocene Language Specification (FLS) was developed to fill this gap, and it’s now a publicly maintained document. But it covers the language as-is rather than serving the formal verification role that a language like Ada was designed for from the start. Teams in safety-critical domains have noticed.
There is no MISRA Rust equivalent, no widely adopted set of coding guidelines for safety-critical Rust analogous to what MISRA C provides for C. Various organizations have published internal guidelines, and the Rust community has discussed the problem, but the gap persists.
Embedded Rust: Mature Foundations, Uneven Coverage
Embedded Rust has come a long way. The embedded-hal project provides a standard set of traits for hardware abstraction, making it possible to write drivers that work across microcontroller families. embassy brings async Rust to embedded targets with an executor designed for low-resource environments. probe-rs provides a solid debugging interface. The foundational work is real.
The coverage problem is that most of the mature ecosystem targets the most popular microcontrollers: STM32 families, nRF5x, RP2040, ESP32 in recent years. Engineers working with less common chips find that the HAL implementation either doesn’t exist, is incomplete, or lags the upstream embedded-hal API version significantly. Maintaining a HAL implementation for a low-volume chip with a small community of users is unglamorous work, and the ecosystem reflects that.
Async embedded is specifically still in a state of rapid change. Embassy’s API has stabilized considerably, but documentation and examples don’t yet match what’s available for synchronous embedded Rust. Teams starting new embedded projects today face a choice between the more documented sync model and the async model that’s increasingly where the community attention is going.
What the Pattern Actually Reveals
The survey’s most useful finding is structural rather than specific: Rust’s challenges are load-bearing. Compilation slowness is a direct consequence of the analysis Rust’s compiler has to do. Async complexity reflects a genuinely hard problem in type-safe concurrent programming. Certification gaps exist because building qualified toolchains is expensive, time-consuming work that post-dates the language. Embedded ecosystem unevenness is the natural result of a volunteer-driven ecosystem covering a long tail of hardware.
The engineering manager quoted in the survey got it right: “If all the things laid out were done, I’d be a happy Rust programmer. If not, I’d still be a Rust programmer.” The challenges are real and the language is still worth it, and those two things aren’t in tension. Understanding the full shape of the friction, rather than just the onboarding story, is what lets teams invest in the right improvements and plan more realistic adoption paths.
The Rust team publishing this kind of honest analysis is itself meaningful. Languages that last are the ones where the maintainers are willing to name problems that don’t have easy solutions.