· 6 min read ·

From Ownership to Async: Where Rust's Friction Lives at Each Stage of Expertise

Source: rust

The Rust team published research this week based on interviews with developers across skill levels and domains. Their central finding challenges the way the Rust community typically talks about adoption difficulty.

The standard framing goes like this: Rust is hard at first because of the borrow checker and ownership model, but once you internalize those concepts, the language gets out of your way. This framing is not wrong; it is incomplete. What the team found is that beginner challenges give way to a different set of expert challenges rather than to smooth sailing. The friction stratifies by domain and experience level, and that distinction matters enormously for how organizations should plan their Rust adoption.

The One Challenge That Follows Everyone

Before getting to the domain-specific problems, there is one challenge that every cohort cited regardless of experience level: compilation times.

The research includes a direct quote contrasting Java’s roughly 100ms compilation with Rust’s substantially longer build times. That gap is structural. Rust’s compilation model involves monomorphizing generics per crate, running LLVM’s optimization pipeline, and linking outputs that can be significantly larger than equivalent C code. A project like ripgrep (around 30k lines) can take 20 to 40 seconds for a clean build on typical developer hardware.

The rustc-perf dashboard tracks this continuously across real-world crates. Over the past year, median full-build times improved roughly 15 percent, driven by several parallel efforts. The parallel front-end (-Z threads=N) parallelizes name resolution and type checking across crates and launched in nightly around Rust 1.76. The Cranelift backend (rustc_codegen_cranelift) skips LLVM entirely for debug builds and shows 30 to 50 percent faster compile times in some benchmarks, though it remains experimental. Switching linkers helps substantially: mold on Linux reduces link times by a factor of two to five compared to the system default, and link time is often dominant in incremental builds.

None of this fully closes the gap with languages like Go or Java, where compilation feels instantaneous on most projects. Rust’s architecture trades compile time for runtime performance, and that trade-off is unlikely to reverse. The real question for any team is whether that tax competes with the productivity benefits Rust delivers elsewhere in the stack.

The Async Trap

For network developers and backend engineers, the challenges that emerge after learning ownership are clustered around async. Rust’s async model is zero-cost at runtime but exposes a surface area that is genuinely difficult to work with at scale.

The core tension is that Rust’s async functions compile down to state machines, and those state machines must satisfy the same ownership and borrowing rules as synchronous code. In a multi-threaded runtime like tokio, spawned tasks must implement Send, which means holding a non-Send type across an .await point produces type errors that can be hard to interpret. The compiler error often points to the wrong location and requires understanding the generated state machine to diagnose correctly.

The stabilization of async fn in traits in Rust 1.75 (December 2023) eliminated one major pain point. For years, async trait methods required the async-trait proc-macro crate, which introduced heap allocation and type erasure that conflicted with zero-cost goals. Native support removed that dependency for most cases. Dynamic dispatch with async traits (dyn Trait with async methods) still requires workarounds through the dynosaur crate or manual boxing, and the story there is still developing.

Runtime fragmentation compounds everything. tokio dominates the ecosystem, but async-std and smol exist with different design philosophies and sometimes incompatible abstractions. A library crate that takes a hard dependency on tokio is opinionated in ways its users may not want, yet abstracting over runtimes adds its own complexity. The Rust team has discussed an async runtime interface in the standard library, but nothing has stabilized.

tokio-console provides a live view of task states and scheduling, which helps, but it remains a separate tool rather than something integrated into the compiler toolchain. Stack traces from async code are still composed of compiler-generated state machine frames that are hard to map back to source locations.

The Certification Gap

Safety-critical domains have a challenge that neither experience nor ecosystem maturity fully addresses: you need your toolchain certified, not just correct.

Ferrocene, developed by Ferrous Systems and AdaCore, is the primary effort here. It is a qualified Rust toolchain targeting automotive (ISO 26262 ASIL-D) and industrial (IEC 61508 SIL 4) applications. It is not rustc; it is a downstream fork maintained with the qualification evidence that certification bodies require, including safety manuals, documented compiler behavior, and structured test suites.

The Safety-Critical Rust Consortium, established by the Rust Foundation in 2024, brought together BMW, Arm, Toyota Research Institute, and others around exactly this problem. MISRA published its first Rust coding guidelines in 2024, which matters because MISRA compliance is a baseline expectation in many automotive and aerospace contexts.

What is still missing is formal verification tooling at the maturity level of SPARK for Ada. The Kani model checker from Amazon Web Services can verify safety properties of Rust code using bounded model checking, but it is not production-certified and does not yet cover all language features a real-world safety-critical project requires. Ada with SPARK has decades of tooling, process, and institutional trust that Rust cannot replicate quickly regardless of the language’s technical properties. For teams operating in this space, the gap between Rust’s theoretical safety guarantees and certified toolchain coverage is a real constraint on adoption timelines.

The Embedded Maturity Problem

Embedded developers occupy a specific uncomfortable position: Rust’s ownership model maps onto hardware resource management well in theory, but the ecosystem around it is uneven in practice.

The embedded-hal trait library provides a hardware abstraction layer that chip-specific implementations should target, enabling portable driver code. Major vendors including STMicroelectronics, Nordic Semiconductor, and NXP have published HAL crates. Quality and completeness vary. A developer targeting an STM32F4 will find more Rust support than one targeting a less common microcontroller, and neither will find the vendor-provided tooling and example coverage that exists in the C ecosystem.

Getting a working embedded Rust toolchain involves selecting the right target triple (e.g., thumbv7em-none-eabihf for Cortex-M4 with hardware floating point), configuring a memory.x linker script, and setting up a probe runner like probe-rs. The defmt crate handles deferred formatting for embedded logging in a way that keeps binary size manageable, but it is a third-party solution.

RTIC (Real-Time Interrupt-driven Concurrency) represents the embedded ecosystem’s strongest argument for Rust’s unique value: it uses the type system to enforce that shared resources are accessed safely across interrupt priorities, catching data races at compile time. That capability has no direct equivalent in C. The type system does work here that comments and runtime checks cannot. But RTIC is one framework among several, and the surrounding ecosystem is still developing compared to what C and C++ developers have access to from their chip vendors.

What This Research Changes About the Adoption Conversation

The value of the Rust team’s framing is that it shifts the adoption conversation from a generic difficulty discussion to something more specific. A team building network services will hit async complexity. A team targeting automotive will need Ferrocene and a certification strategy before writing a line of production code. A team doing bare-metal embedded will spend significant time on toolchain setup and ecosystem gaps that have nothing to do with the borrow checker.

That specificity is more actionable than the standard warning that Rust is hard. It also explains why the research’s quoted engineering manager lands the way it does: if all the named improvements shipped, great; if not, still Rust. The challenges are concrete enough to plan around, and the language’s fundamental properties are compelling enough that the analysis still comes out positive.

The compilation speed work, the async stabilization efforts, Ferrocene’s certification progress, and the embedded HAL ecosystem are all moving in the right direction. That momentum is documented in the research rather than assumed. The Rust team’s willingness to name the friction points plainly, rather than frame them as temporary beginner problems, is itself useful information about how seriously the community is taking the gap between where the language is and where it needs to be.

Was this interesting?