That is, the Rust never
type (!
).
Everything is an expression
Rust is kind of an "everything is an expression" type of language. An expression is something that produces a value. Not everything is an expression, but basically everything that is in a Rust body (executable code) is an expression. Actually, one thing that isn't are locals, like:
let x = 5;
That is its own statement! But you can have an expression with a let
statement, just take:
if let Some(num) = func_that_returns_option() {}
The let
statement in there isn't a local, it's a let
expression.
Let's go over a few other things that are also expressions:
{ 1 }
loop { break 1; }
if true { 1 } else { return; }
This last one is what we'll be interested in.
The never
type
So if we look into the last example I gave, you know the if
block evaluates to 1
. But what about the else
block? return;
doesn't produce a value, it actually leaves the function. So what is its type?
That's right, the never
type!
The point of the never
type is to say "this computation will not complete." That way, if you assign something to that value:
let x = if true { 1 } else { return; }
The type of x
is based on the if
block. But, that doesn't mean the never
type is ignored. How the compiler figures that out is never
can be coerced into any type. That means, when trying to infer the type for x
, the compiler sees the if
block has type i32
. Then the else
block has type never
, so to make it consistent, it says "never
can convert to i32
" and it's all okay!
What does this mean for the user?
Well, not much. It's mostly a fun compiler intricacy. But, if you want to see it mentioned in your diagnostics, try the new let else
syntax (currently only available with nightly). This syntax lets you use a pattern for a let
binding that may not always be true, like:
let Ok(x) = returns_result() else { warn!("This is bad!"); return; };
So, if returns_result
returns Ok
, then we assign x
to the value in Ok
. But, if it's not, we warn and return. Pretty easy!
The type of that else
block has to be never
, though.
So try making it not, with this minimal example:
#![feature(let_else)]
fn opt() -> Option<i32> {
Some(1)
}
fn main() {
let Some(x) = opt() else { 1 };
}
This will error:
error[E0308]: `else` clause of `let...else` does not diverge
--> letelse.rs:7:30
|
7 | let Some(x) = opt() else { 1 };
| ^^^^^ expected `!`, found integer
|
= note: expected type `!`
found type `{integer}`
= help: try adding a diverging expression, such as `return` or `panic!(..)`
= help: ...or use `match` instead of `let...else`
error: aborting due to previous error
For more information about this error, try `rustc --explain E0308`.
We expect that else
block to diverge, so it has to be the never
type!
Cool.
Rust will never
return from that.
Top comments (2)
Really, this is allowed? I mean.. Any sensible compiler would normally just optimize the entire code it would never get to away. Why have code that never does anything in the binary? I seriously doubt that it doesn't. It'd probably also unroll the loop or branching statement out
Well it's not that it will never get executed. It's that an expression with the
never
type will never continue executing after that point. It's mainly used in cases where one branch will return a value, but another branch won't continue executing.Though in a similar sense to what you're referring to, it can be used in a return type like
exit
. It's like a C++[[no return]]
annotation. When it gets to LLVM it passes that info along to do optimizations like you mention. But for the most part (with some exceptions) optimizations like that will happen during code gen, not during a phase that needs to be Rust specific (eg semantic analysis).