Rust’s never type — the ! used for functions that never return — is now stable, and Infallible becomes an alias for it. The change is tiny and the type has existed internally for years. The interesting part is what it took to ship it, which LWN’s write-up documents in detail.
What the never type is for:
- Generics get cheaper. A
FromStrimpl whose conversion cannot fail can settype Err = !, keeping the same generic interface while the compiler strips the error branch as unreachable. - Type inference stays uniform. Rust treats
ifand loops as expressions, so the compiler needs some type for the result of an infinite loop rather than a special rule. !coerces to any other type, which is sound precisely because the value can never exist — dead-code elimination driven by the type system.
The hard part was never fallback:
- For
let f = || { loop {} }; f()?;the compiler cannot infer a concrete type, so a designated fallback applies:()before the 2024 edition,!from it onward. - Changing a fallback changes inference, so it is breaking by definition — and the maintainers still wanted the new behaviour backported to older editions.
- A second change, redefining
Infallibleas an alias for!, is quietly breaking in the same places. The two breakages nearly cancel out, which is what made the pair shippable at all. - Proof came from crater, which compiles all of crates.io against the candidate compiler: 3,300 crates negatively impacted, only seven genuinely broken — because Rust had been warning about never-type fallback since 2024, so most libraries had already adjusted.
- The residual failure mode is mundane.
foo()?on a genericT: Defaultused to infer()and now infers!, which does not implementDefault; the fix is an explicitfoo::<()>()?. Backports into popular but abandoned library versions repaired 1,553 of the failing crates.
The lesson is about process, not type theory. A change that had been planned for years and warned about for two shipped only after a full-ecosystem compile run, negotiation with library maintainers, and backports into versions some of those maintainers had declared end-of-life. Seven crates stayed broken and the change went ahead anyway, with documented escape hatches: stay on the previous compiler, update dependencies, or annotate the affected return types. That is a defensible line — and a useful template for anyone making a breaking change in a widely consumed API.