· 6 min read ·

Compilation, Async, and Certification: The Rust Challenges That Don't Fade With Experience

Source: rust

The conventional wisdom about Rust has a comfortable arc: suffer through ownership and lifetimes, emerge on the other side, and write fearless systems code indefinitely. The Rust team’s recent challenges survey complicates that arc considerably. What they found is that the challenges transform with expertise, shifting from syntactic and semantic learning into compilation performance ceilings, async type system complexity, and domain-specific ecosystem gaps that experience alone cannot bridge.

This finding matters for anyone planning Rust adoption in a production context. The beginner struggle is well-documented and well-addressed by learning resources, the Book, and community tooling. The expert struggle is less charted, less resourced, and more resistant to the standard solutions.

Compilation as Infrastructure Cost

The one challenge that cuts across every segment of the Rust user population is compilation time. The survey describes it as “the universal productivity tax,” and the framing fits. Every cohort they analyzed, from novices to staff engineers, from embedded developers to web service teams, cited build times as a significant barrier.

The numbers make this concrete. A Java compilation baseline sits around 100 milliseconds. Rust builds of non-trivial crates run into tens of seconds for incremental changes, and clean builds of large dependency graphs can exceed minutes on standard hardware. The structural reasons are well understood: whole-crate monomorphization, LLVM’s thorough optimization pipeline, and the serial nature of per-crate compilation units all compound each other.

The tools available to address this are real but limited in scope. cargo check runs only the frontend passes and skips binary generation, giving you borrow checker feedback without waiting for codegen. The Cranelift backend trades optimization quality for speed in debug builds, accessible via nightly with CARGO_PROFILE_DEV_CODEGEN_BACKEND=cranelift. Parallel frontend work has been incrementally stabilized. Incremental compilation helps in many cases, though it has had historical correctness issues that make teams cautious about relying on it.

What these mitigations share is that they address symptoms within the existing compilation model rather than the model itself. A team with a 45-second clean build can improve that to 25 seconds through careful configuration, but the ceiling imposed by Rust’s compilation architecture is structural. Go made compilation speed a first-class design goal from the start, achieving near-instant builds through deliberate architectural choices and forgoing certain runtime optimizations. Rust prioritized runtime performance and safety guarantees, and the compilation cost is one of the consequences of those priorities.

For development loops that involve frequent, small changes, the compile-check-edit cycle remains a meaningful drag. Embedded developers who reflash physical hardware as part of their workflow feel this acutely. Web service teams running dozens of microservices feel it in CI time and infrastructure cost. The problem presents differently by domain, but it persists across all of them.

Async Rust as a Second Language

The survey found that async complexity specifically affected network developers, and the characterization is apt. Async Rust is not a shallow extension to the core language; it introduces a parallel type system with its own ownership semantics, lifetime rules, and failure modes.

async fn in traits was only stabilized in Rust 1.75, released December 2023. Before that, working with async trait methods required the async-trait proc macro, which introduced heap allocation on every call and obscured the actual types involved. The stabilization resolved that particular friction point, but the broader async complexity remained largely intact.

The ecosystem fragmentation around runtimes is a structural issue with no clean resolution. Tokio dominates production use, but async-std, smol, and embassy serve different niches. Libraries written against one runtime’s task system don’t interoperate cleanly with another. This surfaces at library boundaries, when you need to use a crate that makes different runtime assumptions than the rest of your stack.

Pin<P> and Unpin represent a conceptual tax on anyone who needs to implement Future manually or work with futures combinators at a low level. The semantics are correct and necessary, preventing self-referential struct invalidation, but the error messages they produce are among the least intuitive in the language. Async drop remains unstabilized, which means that resources requiring async cleanup, database connections, sockets, file handles, require explicit .close().await calls or wrapper types. That compounds in larger codebases and creates a category of resource leak bugs that synchronous Rust’s Drop trait would handle automatically.

The cancellation semantics are a related source of subtle bugs. Dropping a future cancels it, per the Future::poll contract. Code that acquires a lock inside a future and yields at an .await point can deadlock silently when cancelled. The type system does not catch this. Teams new to async Rust regularly discover this class of bug in production rather than development, and the tokio-console and Loom tooling exists specifically to help surface these issues, though neither is integrated into most teams’ standard workflows.

The Certification Gap

For safety-critical development, the survey surfaces a challenge about ecosystem infrastructure rather than language features. Rust’s memory safety guarantees make it attractive for automotive, aerospace, and industrial control contexts. The language-level argument is strong; the toolchain and ecosystem infrastructure for regulated industries is catching up but remains incomplete.

Ferrocene, developed by Ferrous Systems and later supported by AdaCore, provides a qualified toolchain for ISO 26262 ASIL D and IEC 61508 SIL 4 contexts. This is a significant engineering achievement. The qualification scope is narrow by necessity: specific compiler versions, specific target platforms, specific language feature subsets. An organization adopting Ferrocene for automotive work still needs to understand those scope limitations, which requires engineering effort and legal review beyond just selecting a toolchain.

The Ferrocene Language Specification is now publicly maintained, providing a more rigorous language definition than the prior behavior-as-specification approach. There is no widely adopted MISRA Rust equivalent yet. Organizations doing DO-178C work for aerospace have no clear toolchain qualification path beyond what they can construct themselves. MATLAB/Simulink generates C code with established certification workflows; no Rust equivalent exists for model-based design in regulated industries.

This gap reflects the state of Rust’s ecosystem maturity in these domains more than any inherent language limitation. Ada faced similar ecosystem immaturity relative to C in regulated industries for years before the tooling and certification infrastructure caught up. Rust’s safety-critical ecosystem is earlier in that trajectory, and the Rust team’s honest acknowledgment of the gap signals they understand what production adoption in these domains actually requires.

Embedded Ecosystem Unevenness

The embedded situation is more optimistic but still uneven in ways that matter for hardware selection decisions. embedded-hal provides a standard trait abstraction for hardware peripherals, enabling driver crates to work across different microcontroller families. Popular platforms, STM32, nRF5x, RP2040, ESP32, have solid HAL implementations with active maintenance. The embassy async executor is purpose-built for resource-constrained environments and has stabilized considerably since its early days.

The problem is coverage outside the popular platforms. Less common microcontrollers often have HAL implementations in various states of completion, maintained by one or two individuals, sometimes abandoned mid-implementation. A team selecting hardware for a new embedded project has to weigh Rust ecosystem support as a factor, which can constrain hardware selection in ways that don’t apply when writing in C.

Documentation for async embedded patterns lags the synchronous equivalents. Teams working with embassy face a choice between the better-documented synchronous model and the community-forward async model with fewer reference examples. That gap is narrowing but visible in the ratio of available resources and the frequency of questions in embedded Rust community spaces.

What Self-Assessment Signals

The Rust team publishing this kind of analysis, acknowledging compilation costs, async complexity, certification gaps, and embedded unevenness, reflects a language that has moved past needing to attract developers by minimizing its difficulties. The source report quotes an engineering manager adopting Rust for performance: “If all the things laid out were done, I’d be a happy Rust programmer. If not, I’d still be a Rust programmer.” That is the profile of a language with genuine traction in its domain, past the point where softening the narrative serves anyone.

The conventional borrow checker story served a purpose for the first several years of Rust’s adoption, giving beginners a specific conceptual hurdle to target. The survey suggests the language is past the point where that framing serves production users well. The challenges that matter most for production adoption live in the areas that experienced Rust teams encounter: compilation costs that accumulate across CI pipelines, async semantics that create bugs the type checker cannot catch, and regulated industries that require certification documentation still being written.

Rust remains, by most accounts, worth those costs. The memory safety guarantees are real, the performance is real, and the tooling has improved substantially over ten years. The survey’s value is in making clear what “worth it” actually means for teams at different stages and in different domains, rather than letting the borrow checker story stand in for the whole picture.

Was this interesting?