The Never Type ! and Infallible
The Never Type ! and Infallible
Thesis
Rust has always had a type for “this expression does not produce a value”: !, the never type. panic!(), loop {} without break, return, continue, and std::process::exit all have type !, and a function declared -> ! promises never to return. Until now you could only write ! in that return position. Everywhere else — Result<T, !>, type Error = !;, impl Trait for ! — it was an unstable feature, and the standard library used a stand-in, the empty enum std::convert::Infallible.
Rust 1.100 (scheduled for 2026-11-12) stabilizes ! as an ordinary type after a decade behind a feature gate, and makes Infallible a plain alias for it. The language change is small; the error-handling consequence is real: “this operation cannot fail” now has one canonical spelling, Result<T, !>, that the compiler understands well enough to let you skip the impossible Err arm entirely.
The same release changes never type fallback to ! on every edition, which is a small breaking change for code that quietly relied on the old fallback to (). This chapter shows both sides, compiled with the current beta and stable toolchains.
Mental model
┌──────────────────── every type ────────────────────┐
│ u32 String () Vec<T> Result<T, E> … │
└───────────────────────────▲─────────────────────────┘
│ coerces to any type
│ (unreachable code needs no value)
`!` — no values at all
Result<T, !> = Ok(T) | Err(<impossible>) → only Ok can exist
size_of::<Result<u32, !>>() == size_of::<u32>() → no discriminant needed
Infallible = ! (type alias since 1.100)
Three facts carry the whole chapter:
| Fact | Consequence |
|---|---|
! has no values |
Code that “holds” one is unreachable; the compiler can delete branches that need it |
! coerces to any type |
A diverging arm (die(…), panic!, return) fits in a match whose other arms are u32 |
An enum variant containing ! is uninhabited |
let Ok(v) = r; is irrefutable when r: Result<T, !>; the Err arm need not be written |
Infallible was the library’s way to get the same effect (an enum with no variants). Since it is now literally !, existing APIs such as TryFrom implementations whose error is Infallible work unchanged and read the same way.
Worked examples
All listings were compiled and run on the box with rustc 1.100.0-beta.4 (2026-10-04) and, for comparison, rustc 1.99.0 stable, both installed with rustup. Until 1.100 reaches stable, run them with cargo +beta / rustc +beta:
rustup toolchain install beta --profile minimal
rustc +beta --versionrustc 1.100.0-beta.4 (a2eb6097d 2026-10-04)
Case 1: ! as a type — cannot-fail results, irrefutable Ok
cargo +beta new never-demo && cd never-demo// src/main.rs — the never type `!` as a first-class type (Rust 1.100)
use std::convert::Infallible;
use std::process;
// Case 1: a diverging function. `-> !` has been legal here since Rust 1.0.
fn die(msg: &str) -> ! {
eprintln!("fatal: {msg}");
process::exit(2)
}
// Case 2: `!` written anywhere a type goes — new in 1.100.
type Never = !;
fn parse_port(s: &str) -> Result<u16, Never> {
// This parser cannot fail: anything unparsable maps to the default port.
Ok(s.parse().unwrap_or(8080))
}
// Case 3: a trait whose implementors pick an error type; `!` means "cannot fail".
trait Source {
type Error: std::fmt::Debug;
fn read(&mut self) -> Result<u32, Self::Error>;
}
struct Counter(u32);
impl Source for Counter {
type Error = !; // reading a counter never fails
fn read(&mut self) -> Result<u32, !> {
self.0 += 1;
Ok(self.0)
}
}
fn main() {
// `!` coerces to any type, so a diverging arm fits a u32 match.
let args: Vec<String> = std::env::args().collect();
let n: u32 = match args.get(1) {
Some(s) => s.parse().unwrap_or_else(|_| die("not a number")),
None => 3,
};
// Irrefutable: the Err arm of Result<_, !> is uninhabited, so no match needed.
let Ok(port) = parse_port("9000");
println!("port = {port}");
let mut c = Counter(0);
let mut total = 0;
for _ in 0..n {
let Ok(v) = c.read();
total += v;
}
println!("sum of first {n} reads = {total}");
// Infallible is now the same type as `!`.
let same: fn(Infallible) -> ! = |x| x;
let _ = same;
let Ok(wide) = u64::try_from(7u8); // TryFrom via Into: Error = Infallible = !
println!("wide = {wide}");
println!("size_of::<Result<u32, !>>() = {}", std::mem::size_of::<Result<u32, !>>());
println!("size_of::<Result<u32, String>>() = {}", std::mem::size_of::<Result<u32, String>>());
}cargo +beta run -q
cargo +beta run -q -- 5
cargo +beta run -q -- five; echo "exit=$?"port = 9000
sum of first 3 reads = 6
wide = 7
size_of::<Result<u32, !>>() = 4
size_of::<Result<u32, String>>() = 24
port = 9000
sum of first 5 reads = 15
wide = 7
size_of::<Result<u32, !>>() = 4
size_of::<Result<u32, String>>() = 24
fatal: not a number
exit=2
What to notice:
unwrap_or_else(|_| die("not a number"))type-checks because the closure returns!, which coerces tou32.let Ok(port) = …;andlet Ok(v) = c.read();have noelseand nomatch. The compiler knowsErr(!)cannot be constructed. WithResult<u16, String>the same line is a compile error (refutable pattern).Counter::Error = !documents “cannot fail” in the type, and callers that are generic overSourcestill handleSelf::Errornormally.Result<u32, !>is the same size as a bareu32: no tag is stored for a variant that cannot exist.fn(Infallible) -> !accepting|x| xis the alias at work: the parameter is a!.
The same file on 1.99 stable stops at the first ! in type position:
cargo +stable build -qerror[E0658]: the `!` type is experimental
--> src/main.rs:12:14
|
12 | type Never = !;
| ^
|
= note: see issue #35121 <https://github.com/rust-lang/rust/issues/35121> for more information
...
(one such error per ! outside a return position). Until your minimum supported Rust version is 1.100, keep spelling it Infallible in public APIs; code that already uses Infallible keeps compiling and gains nothing by being rewritten.
Case 2: the fallback change — code that relied on () by accident
When the compiler meets a ! it must coerce, but nothing tells it the target type, it picks a fallback. That fallback used to be (); Rust 2024 switched it to !, and 1.100 does the same for editions 2015–2021. The most common casualty is ? on a function that is generic in its Ok type:
// fallback.rs — code that silently relied on `!` falling back to `()`
fn f<T: Default>() -> Result<T, ()> {
Ok(T::default())
}
fn outer() -> Result<u32, ()> {
f()?; // which T? nothing here says
Ok(1)
}
fn main() {
println!("{:?}", outer());
}On 1.99, edition 2021: already rejected by a deny-by-default future-compatibility lint, which names the fix:
rustc +stable --edition 2021 fallback.rserror: this function depends on never type fallback being `()`
--> fallback.rs:6:1
|
6 | fn outer() -> Result<u32, ()> {
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
= help: specify the types explicitly
note: in edition 2024, the requirement `!: Default` will fail
--> fallback.rs:7:5
|
7 | f()?; // which T? nothing here says
| ^^^
= warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in Rust 2024 and in a future release in all editions!
= note: for more information, see <https://doc.rust-lang.org/edition-guide/rust-2024/never-type-fallback.html>
= note: `#[deny(dependency_on_unit_never_type_fallback)]` (part of `#[deny(rust_2024_compatibility)]`) on by default
help: use `()` annotations to avoid fallback changes
|
7 | f::<()>()?; // which T? nothing here says
| ++++++
error: aborting due to 1 previous error
On 1.100 beta, edition 2021 — the lint is gone because the behaviour changed; you get the real type error (identical to what 1.99 already says under edition 2024):
rustc +beta --edition 2021 fallback.rserror[E0277]: the trait bound `!: Default` is not satisfied
--> fallback.rs:7:5
|
7 | f()?; // which T? nothing here says
| ^^^ the trait `Default` is not implemented for `!`
|
= note: this error might have been caused by changes to Rust's type-inference algorithm (see issue #148922 <https://github.com/rust-lang/rust/issues/148922> for more information)
= help: you might have intended to use the type `()` here instead
note: required by a bound in `f`
--> fallback.rs:2:9
|
2 | fn f<T: Default>() -> Result<T, ()> {
| ^^^^^^^ required by this bound in `f`
The fix is to say what you meant:
// fallback_fixed.rs — name the type instead of relying on fallback
fn f<T: Default>() -> Result<T, ()> {
Ok(T::default())
}
fn outer() -> Result<u32, ()> {
f::<()>()?; // say it: the Ok value is ()
Ok(1)
}
fn main() {
println!("{:?}", outer());
}rustc +beta --edition 2021 fallback_fixed.rs && ./fallback_fixedOk(1)
Case 3: the panicking closure
The second common pattern is a closure that only panics, passed to something that requires its return type to implement a trait:
// closure.rs — a panicking closure used to "return ()" by accident
trait Unit {}
impl Unit for () {}
fn run<R: Unit>(f: impl FnOnce() -> R) {
let _ = std::panic::catch_unwind(std::panic::AssertUnwindSafe(f));
}
fn main() {
run(|| panic!("boom"));
println!("survived");
}rustc +beta --edition 2024 closure.rserror[E0277]: the trait bound `!: Unit` is not satisfied
--> closure.rs:10:5
|
10 | run(|| panic!("boom"));
| ^^^^^^^^^^^^^^^^^^^^^^ the trait `Unit` is not implemented for `!`
|
help: the trait `Unit` is implemented for `()`
--> closure.rs:3:1
|
3 | impl Unit for () {}
| ^^^^^^^^^^^^^^^^
= note: this error might have been caused by changes to Rust's type-inference algorithm (see issue #148922 <https://github.com/rust-lang/rust/issues/148922> for more information)
Annotate the closure’s return type: run(|| -> () { panic!("boom") });
rustc +beta --edition 2024 closure_fixed.rs && ./closure_fixedthread 'main' (250345) panicked at closure_fixed.rs:10:20:
boom
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
survived
Case 4: Infallible and ! are one type now
If a crate implemented a trait for both — typically to support nightly users of ! — the two impls now overlap:
// conflict.rs — Infallible and ! are now one type
use std::convert::Infallible;
trait Describe { fn describe() -> &'static str; }
impl Describe for Infallible { fn describe() -> &'static str { "infallible" } }
impl Describe for ! { fn describe() -> &'static str { "never" } }
fn main() { println!("{}", <! as Describe>::describe()); }rustc +beta --edition 2024 conflict.rserror[E0119]: conflicting implementations of trait `Describe` for type `!`
--> conflict.rs:5:1
|
4 | impl Describe for Infallible { fn describe() -> &'static str { "infallible" } }
| ---------------------------- first implementation here
5 | impl Describe for ! { fn describe() -> &'static str { "never" } }
| ^^^^^^^^^^^^^^^^^^^ conflicting implementation for `!`
Delete one of them. Keep the Infallible impl if you still support compilers older than 1.100: it covers ! on new compilers for free.
Where ! belongs in error handling
| Situation | Spell it | Why |
|---|---|---|
Function that never returns (exit, exec, server loop) |
fn run() -> ! |
Callers can use it in any expression position |
| Trait with an associated error type; this impl cannot fail | type Error = !; |
Callers generic over the trait still compile; concrete callers write let Ok(v) = …; |
| Public API that must build on Rust < 1.100 | Infallible |
Same type on new compilers, still compiles on old ones |
| “Can’t fail yet” — you expect failures later | A real error type | Changing ! to MyError later is a breaking API change |
Converting between error types with ? |
impl From<Infallible> for MyError if needed |
Works for ! too, since it is the same type |
The trap
Relying on inference to pick () for you. Code like f()?;, run(|| panic!()), or if c { Default::default() } else { return } compiled for years because the never type quietly became () when nothing else constrained it. Under 1.100 (any edition) or edition 2024 that guess is !, and the code either fails to compile — the good case, shown above — or, in the rare unsafe case the never_type_fallback_flowing_into_unsafe lint exists for, picks a different type than the author assumed. The compiler’s 1.99 lint (dependency_on_unit_never_type_fallback) points at every affected function: fix those before you bump the toolchain, not after.
The second, quieter trap is using ! as a placeholder in a public trait impl or function signature because “it can’t fail today”. ! is a promise. Replacing it with a real error type later breaks every caller who wrote let Ok(v) = …;.
The boring rule
Use -> ! for functions that truly never return and Result<T, !> (or Infallible until your MSRV is 1.100) only where failure is impossible by construction. Never lean on fallback: when the compiler cannot see a type, write it — f::<()>()?, || -> () { … }, () = expr;. Turn on the rust_2024_compatibility lints on older editions and clear them before upgrading.
Try this
- Change
parse_portto returnResult<u16, String>. Which lines stop compiling, and what does the compiler suggest forlet Ok(port) = …;? - Print
size_of::<Option<!>>()andsize_of::<Result<!, String>>()and explain the numbers. - Write
fn absurd<T>(x: !) -> T { x }and use it to implementfn into_ok<T>(r: Result<T, !>) -> Twith amatchthat has a single arm. - Take a crate you maintain on edition 2021, run
cargo +stable check(1.99 today) and look fordependency_on_unit_never_type_fallback; thencargo +beta checkand confirm every site is fixed. - Find a
TryFromimpl instdwhoseErrorisInfallibleand destructure its result with an irrefutablelet.
Sources
- rust-lang/rust PR #155499, “stabilize never type” (merged 2026-08-24): https://github.com/rust-lang/rust/pull/155499
- Draft release notes for Rust 1.100.0 (stabilize never type;
Infalliblealias; fallback on all editions): https://github.com/rust-lang/rust/issues/163811
- Tracking issues: never type https://github.com/rust-lang/rust/issues/35121, fallback change https://github.com/rust-lang/rust/issues/148922
- The Rust Edition Guide — Never type fallback change: https://doc.rust-lang.org/edition-guide/rust-2024/never-type-fallback.html
- The Rust Reference — Never type: https://doc.rust-lang.org/reference/types/never.html
- Standard library — primitive
never: https://doc.rust-lang.org/std/primitive.never.html;std::convert::Infallible: https://doc.rust-lang.org/std/convert/enum.Infallible.html
- Rust Project Goals 2026 — Stabilize never type: https://goals.rust-lang.org/2026/stabilize-never-type.html
- LWN, “Stabilizing Rust’s never type” (2026-09-08): https://lwn.net/Articles/1091015/