The standard account of Rust’s difficulty runs like this: the borrow checker is hard to learn, but developers who push through it end up with a fluent, productive experience. The hard part comes first and goes away.
A survey published by the Rust project team complicates this. Across cohorts ranging from beginners to experts, from embedded developers to network engineers, the team found that challenges do not disappear with experience. They shift. Beginners struggle with ownership concepts; experts face domain-specific problems that beginners haven’t reached yet. The engineering manager quoted in the report put it plainly: “If all the things laid out [to make Rust better] were done, I’d be a happy Rust programmer. If not, I’d still be a Rust programmer.”
That attitude is widespread enough that Rust adoption continues. The friction is tolerated because the value proposition holds. But understanding where the friction lives, and why, matters for anyone thinking about where Rust goes next.
Compilation Time Is the One Universal Problem
Every cohort in the survey, regardless of experience level or domain, cited compilation times as a productivity barrier. The comparison offered is blunt: Java compiles in approximately 100 milliseconds; Rust compiles in amounts of time large enough to interrupt flow.
The reason is not mysterious. Rust’s compilation model does work that most other languages defer or skip. Generic monomorphization produces specialized machine code for each concrete type a generic function or struct is instantiated with, which produces better runtime performance at the cost of more work at compile time. The borrow checker runs a constraint-solving pass over the entire program. Everything then feeds through LLVM’s optimization pipeline, which is thorough and slow by design.
Go compiles fast because it made deliberate sacrifices: no generics for most of the language’s history (they arrived in 1.18), a simpler type system, and a custom backend optimized for compilation speed rather than code quality. Rust’s authors chose the other end of that tradeoff and have been paying the compile-time cost ever since.
The Rust team has been attacking this from multiple directions. Incremental compilation, which avoids recompiling unchanged code, has been in place since 1.24. Pipelined compilation, which allows downstream crates to begin compiling against a dependency’s metadata before that dependency finishes codegen, shipped in Rust 1.38. A parallel query system for the front-end, which parallelizes the type-checking and analysis work, has been in development for years and is available today on nightly under RUSTFLAGS="-Z threads=8".
The Cranelift codegen backend represents a different approach: replace LLVM entirely for debug builds with a backend that produces less optimized code much faster. For tight development loops where you care about correctness more than performance, Cranelift can cut debug build times substantially. It is not yet stable, but it is getting closer.
Practical teams working around slow build times tend to reach for the same toolkit: cargo check for fast type-checking feedback without codegen, splitting large monolithic crates into smaller ones to improve incremental granularity, and sccache for shared build artifact caches across a team. None of this eliminates the problem. A large codebase with heavy generic use and procedural macros can still take minutes from a cold build.
Async Rust: Where Experienced Developers Lose Their Footing
Network developers are often the most productive Rust practitioners once they get past the basics, because the async ecosystem has matured considerably. async fn in traits was stabilized in Rust 1.75 in December 2023, ending years of dependence on the async_trait procedural macro, which solved the problem but introduced a heap allocation per call.
The stabilization of async functions in traits removed one of the most cited pain points for library authors. Writing generic async interfaces no longer requires boxing every return type or restructuring designs around the limitation. That is real progress.
But the complexity underneath has not gone away. The Send bound problem trips up experienced developers consistently. In a multithreaded async context, futures need to implement Send so that the executor can move them between threads. A future is only Send if every type it holds across an .await point is also Send. A MutexGuard is not Send, so holding one across an await makes the containing future not Send, and the error message Rust produces for this situation is considerably less helpful than its ordinary ownership errors:
// This fails to compile in a Send-required context
async fn process(data: Arc<Mutex<Vec<u8>>>) -> Vec<u8> {
let guard = data.lock().unwrap();
fetch_remote_data().await; // guard held here, future is not Send
guard.clone()
}
// Fix: ensure the guard is dropped before any await point
async fn process_fixed(data: Arc<Mutex<Vec<u8>>>) -> Vec<u8> {
let cloned = {
let guard = data.lock().unwrap();
guard.clone()
}; // guard drops here
fetch_remote_data().await;
cloned
}
The executor fragmentation problem has also not gone away. Tokio has become the de facto standard for production network code, but “de facto” is not the same as “standard.” Libraries written against Tokio’s TcpStream do not work directly with async-std’s. Code that uses Tokio’s timer primitives cannot be trivially moved to a different runtime. The standard library deliberately excludes an async runtime, which is a reasonable design choice for a systems language, but the consequence is that the ecosystem converged rather than standardized, and that convergence is imperfect.
Safety-Critical Certification: Years of Process Work Ahead
Teams building software for automotive, industrial, or medical applications face a challenge with no software-quality solution. Safety-critical domains require tool qualification: formal documentation proving that the compiler produces correct output according to its specification, reviewed to the satisfaction of standards bodies. ISO 26262 governs automotive safety up to ASIL D. IEC 61508 covers industrial systems. DO-178C covers avionics.
Ferrocene, developed by Ferrous Systems, achieved qualification for ISO 26262 ASIL D and IEC 62443 in late 2023. This is a significant milestone and the Rust community should treat it as one: there is now a qualified Rust compiler toolchain in existence, something that did not exist two years ago.
A qualified compiler is necessary but not sufficient. The qualification covers the compiler; it does not extend to the standard library, third-party crates, or the rest of the toolchain such as the linker and debugger. Teams building certified products need their entire supply chain qualified. For C and Ada, decades of established toolchains, certified libraries, and documented vendor processes exist. SPARK Ada in particular has a mature story for safety-critical formal verification that Rust’s ecosystem will take years to match in depth.
The Rust Foundation’s safety-critical working group is producing guidelines and coordinating with vendors, which is the right structural approach. But the gap is real and is measured in years of careful process work that no amount of language improvement can shortcut.
Embedded: Mature in the Middle, Thin at the Edges
Embedded Rust has a story that is more encouraging than the certification situation, though it has a similar shape: strong coverage in the center, thinner as you move toward less common hardware.
The Embassy async framework provides a no-std async runtime for bare-metal microcontrollers, solving the problem of doing IO-style work without an operating system in a way that feels ergonomic rather than hostile. probe-rs handles debugging and flashing across a wide range of debug probes. The embedded-hal trait system standardizes peripheral access across HAL implementations, making it possible to write drivers that work across different hardware targets.
For popular microcontroller families, particularly STM32 parts, Nordic nRF series, and RISC-V targets, the ecosystem is usable and improving steadily. Embassy in particular has seen significant adoption because it makes async embedded Rust tractable without a significant overhead penalty.
The problem the survey identifies is not absence but incompleteness. Peripheral Access Crate coverage, which generates safe register-level access code from vendor-provided SVD description files, is good for mainstream parts and inconsistent for everything else. A developer working with a niche microcontroller may need to generate PAC support from a vendor SVD file that has errors in it, or write register access code by hand against a datasheet. That is not a Rust problem specifically; it is an ecosystem maturity problem, and it compounds with the certification gap for teams in safety-critical embedded domains.
What This Research Points Toward
The value in surveying Rust’s challenges at this level of specificity is that it separates problems with different solutions. The conventional framing of “Rust is hard to learn” points toward documentation and tutorials. That work has already happened: The Rust Book, Rustlings, and Rust by Example are genuinely excellent learning resources, and beginner accessibility has improved substantially over the past several years.
The challenges that persist past the beginner stage require different interventions. Compilation times require engineering work on the compiler, build system, and toolchain. Async complexity requires continued language-level work, some of which is already in progress. Certification gaps require coordinated process work between the Rust Foundation, toolchain vendors, standards bodies, and the companies doing safety-critical development. Embedded ecosystem maturity requires library authors, hardware vendors, and contributors working across a long tail of hardware targets.
None of these are unsolvable. The compilation time trajectory is positive, with parallel front-end work and Cranelift both making incremental progress. Ferrocene’s qualification milestone shows that the certification path exists even if it is long. Embassy’s rise shows that embedded Rust can be both ergonomic and capable.
The survey’s finding that developers would continue using Rust regardless of whether every improvement gets made is reassuring in one sense and clarifying in another. Rust does not need to fix everything to keep its current users. It needs to fix these things to accelerate adoption by the teams that are currently watching from the sideline, doing the cost-benefit calculation, and deciding to wait.