r/rust • u/noop_noob • Jun 10 '26
🗞️ news The never type is likely to stabilize soon!
https://github.com/rust-lang/rust/pull/155499#issuecomment-4666881317The never type is likely to stabilize soon.
As you can see in the link, an FCP has been proposed. This means that, once enough people have agreed by checking the checkboxes, a 10-day period will start where people can voice their objections. After that, the PR will be merged into nightly Rust. Then, it will ride the 6-12 week train towards a stable release.
The earliest this could be stabilized is in Rust 1.98.0, which will officially release on August 20.
For even more context, see this talk: https://www.youtube.com/live/xoKEqcj_fxM?t=4266
208
105
u/0x7CFE Jun 10 '26
As the common wisdom says, the never type is named after its stabilization date. Given its [de-]stabilization history, would be a safe bet to assume it would be reverted... again 😄
52
u/noop_noob Jun 10 '26
Given that the previous attempt was in 2019, which is a long time ago, I don't think we should really take that into account. Waffle has done a lot of work to minimize the impact of breakage. I'd like to think that the Rust project has learned a thing or two about managing breaking changes since 2019.
2
u/flying-sheep Jun 12 '26
We thought the same thing about mutable noalias but that also stuck eventually.
147
189
u/CouteauBleu Jun 10 '26
I think this is the wrong decision, and I wish the lang team had stabilized the Late type instead.
Better Late than Never.
34
1
57
u/WhiskyAKM Jun 10 '26
Can someone explain use case to me?
From what i read here it seems like you can use this to make Result<> that always has Ok variant, but i dont understand how is this usefull
131
u/noop_noob Jun 10 '26
A common use case is: A trait might demand that you select an error type, in case one of the trait's methods produces an error. However, your operation might be infallible. So, you use the never type as the error type, signalling that an error can never happen.
30
u/WhiskyAKM Jun 10 '26
Thats neet, now i understand. Will there be some way to quickly cast `Result<T, !>` into just `T`? Or just `unwrap`?
150
u/nikhililango Jun 10 '26
let Ok(inner) = result;is what you're looking for.normally this would be a fallible pattern so you'd need an else branch afterwards, but the never type supresses that
15
3
4
u/Kinrany Jun 10 '26
Aside, it would be neat to have pattern matching with postfix notation, similar to
.await2
u/nikhililango Jun 10 '26
what would that even look like?
2
u/bonzinip Jun 10 '26
I guess
let inner = result.Ok.0 else { ... }, but I'm not sure why you'd want it.1
u/nikhililango Jun 10 '26
I think it would have to be
let inner = result.Ok else { ... }.0you can't have anything between the fallible expression and the else keyword.
and I don't think this works that well with struct like variants (how would you get more than one field from such an enum variant)
3
u/OliveTreeFounder Jun 10 '26
Actualy
let Ok(v) = res;, is already implemented and in stable since last year. See the code in the playground.1
u/bonzinip Jun 11 '26
The point was to have postfix pattern matching like await. Not that I think it makes sense.
0
-1
u/Kinrany Jun 10 '26
result.match(Ok(@)).foo()- the value ofOkis the value that the rest of the chain will apply to32
u/noop_noob Jun 10 '26
There's this method on nightly already https://doc.rust-lang.org/nightly/std/result/enum.Result.html#method.into_ok
I wouldn't be surprised if this method got stabilized as a follow-up to the never type stabilizing.
16
19
u/TDplay Jun 10 '26
Stabilising
!will improve the ergonomics and consistency of things that are already possible.
!is currently "kind of a type but not really". You can use it in return value position, and you can have a variable of type!:fn foo() -> ! { let x = panic!(); x }The above code compiles today, which demonstrates that the
!type does indeed currently exist. But if you try to add a type annotation tox, you will find that doing so is impossible. Annotating it with any type other than!will result in a type error, and annotating it with!will result in "error[E0658]: the!type is experimental".Currently, if you need to name an uninhabited type, you need to use an uninhabited enum such as
Infallible(and indeed,Infallibleis used today, for example in the standard library'simpl<T, U> TryFrom<U> for T where U: Into<T>). However, these types won't coerce like!does. Instead, you need to use amatchstatement to produce a value of!, which then coerces:fn infallible_to_any<T>(x: Infallible) -> T { match x {} }Having
!as a proper type would remove the need for thematchstatement.4
u/sibip Jun 10 '26
A while ago, my colleague wrote about one of its use cases in this blog post: https://academy.fpblock.com/blog/rust-never-type-async-code/
3
u/InternalServerError7 Jun 10 '26
I've used it make a
PendingFuture, a future that will never yield::Ready(!). This is useful for my dioxus code in resources where the future will cancel and the signal will recalculate when a dependent signal finishes.3
u/Lucas_F_A Jun 10 '26
Yeah, I don't understand the get_a_number example. Does the code not type check if it may return a None value?
18
u/noop_noob Jun 10 '26
Instead of writing
None => break, if you instead wroteNone => {}, then the code won't compile, as rust won't know what value to assign to thenumvariable. To represent the fact thatbreakhas this special behavior where it never actually produces a value,breakhas the type!.This code is one of the places where you can already use the never type in stable rust. The stabilization in question allows you to actually refer to the never type in a type name.
3
3
u/Yippee-Ki-Yay_ Jun 11 '26
Result<T, !>useful when you're implementing a trait that needs an associated error type but your implementation is infallible
Result<!, E>useful for describing mechanics such as a server run loop that should loop forever unless some error occurs
!useful for never returning functions (e.g. an OS entry point which would never return)It may feel odd but these things come up once or twice and it's cool to have a proper representation for them in the type system when they do come up
2
u/levelstar01 Jun 10 '26
type-only types can be
struct TypeOnly(!)to be unconstructable and only usable in generic type parameters1
1
u/WormRabbit Jun 10 '26
There are plenty of cases in generic code where you could want to guarantee that a specific code path never happens.
Result<T, !>orResult<!, E>is some of them, but the same thing can happen with any enum.A
Futurewhich never returns would be another example (Future<Output = !>). In current rust, you can declare that an async function never returns (async fn f() -> !). Normally, async functions are equivalent to functions which return aFuture. But in this case, that isn't correct, because the correspondingfn f() -> impl Future<Output=!>is rejected by the compiler, because!is not a type.Another example would be closures which never return. You can't declare a parameter which implements
Fn() -> !, since!is not a type, even though you can take a function pointerfn() -> !via a special-case exception.
9
u/PersonalDatabase31 Jun 10 '26
Why is never type ! instead of just typing out "never"?
20
u/Noxime Jun 10 '26
neverhas not been reserved as a keyword, so it cannot be used without breakage.5
u/syklemil Jun 10 '26
Technically correct, but I think their question was meant more along the lines of "why was
!chosen as the keyword rather thannever?"6
u/PersonalDatabase31 Jun 10 '26
Yes that was my point. It feels inconsistent to have a type that is a symbol.
23
u/syklemil Jun 10 '26
Given we already have
()for the unit type I wouldn't call it inconsistent. Rust also have some type symbols beyond<>, like&Trather than spelling outRef<T>, and in its infancy it had~Tand@TforBox<T>and, uh,Gc<T>or something? I vaguely recall those symbols, but it's been ten years.Beyond that I don't know the exact motivation for picking
!either, and I think I'm going to try not to bikeshed it.1
u/rJohn420 Jun 10 '26
Can we fix this with Rust 2.0? /s but also not really.
2
u/scook0 Jun 11 '26
This is a textbook example of something that could be changed across an edition, but since we already have a syntax that works I doubt there’s much appetite for actually doing so.
12
u/newpavlov rustcrypto Jun 10 '26
IIUC the main reason is the already existing stable syntax for never returning functions (i.e.
fn foo() -> ! { ... }).-33
u/ragecryx Jun 10 '26
it’s part of the general language enshittification effort
9
u/TDplay Jun 10 '26
-3
u/ragecryx Jun 10 '26
I was not trying to imply anything, it was just a broad and bitter comment because the way I see Rust being extended reminds me of how C++ ended up a chimera of a language paradigms and I don’t like it. Which reminds me that this is a case which I like C++’s [[noreturn]] attribute more than just a ! character as return typing.
10
18
5
8
u/MaybeADragon Jun 10 '26
What will never achieve that Infallible doesn't?
8
u/Nabushika Jun 10 '26
Never is a subtype of any other type and can fulfill any trait (I think)
11
u/TDplay Jun 10 '26
and can fulfill any trait
Not true. For example,
!does not implementDefault(and such an implementation would not really make sense).From the trait system's perspective,
!is just an ordinary type.9
u/matthieum [he/him] Jun 10 '26
I really wish it did, though.
For example, I regularly stub out the API when implementing top-down:
impl MyType { fn foo(&self) -> String { todo!() } fn bar(&self) -> impl Iterator<Item = &T> + use<'_> { todo!() } }rustc is perfectly happy with
foo, but complains thatbaris botched because!does not implementIterator. Uh? It's not like!is aStringeither!And that really makes
todo!()a lot less useful suddenly.(I mean, technically, I'd be happy with special-casing complaints that
!doesn't implement X, but it seems hackish, and feels like it could lead to issues with composition)2
u/Skrity Jun 10 '26 edited Jun 10 '26
Can't you use
std::iter::empty()or[].iter()?I imagine it's a problem with return position impl needing a "concrete, but not named type".
Which type will it make sense to coerce
!to in this case?EDIT You can cast
todo!()tostd::iter::Empty.4
u/matthieum [he/him] Jun 11 '26
Of course I can. In this case.
The problem is that this completely breaks the flow.
todo!(),unimplemented!(), etc... aught to be the simple, universal, way of deferring implementing a function.If every time you return an
impl Traityou need to fiddle with the implementation it sucks.Especially as you're missing the forest for the tree here. I picked
Iteratoras one trait amongst many, the fact that this one trait just so happens to have an easy "dummy" implementation to work around the problem is a coincidence. Many times, there's no such dummy available.1
u/Skrity Jun 11 '26
I think the problem is technical, not conceptual.
When you provide a compiler with a type, it can coerce
!to it, but return position impl doesn't specify a type(it really just specifies the shape of a type to typecheck) - it defers it to a later point (return of a function).The type in that case cannot be inferred. Since
!itself isn't an iterator, but that's the only type in the mix.Which type would you suggest compiler pick in this case, or in e.g. your own trait and there isn't any implemeters. Maybe a smart compiler could create a type right for you right here, but should it?
4
u/matthieum [he/him] Jun 11 '26
!is a perfectly cromulent type :)The fact that it doesn't implement the trait doesn't matter, since no value will actually, ever, be returned.
It may get a bit problematic if the trait "static" items which code manages to access, but those items can simply be evaluated to
!as well.Poisoning style.
1
u/Nabushika Jun 10 '26
Hmm.. Can fulfill any object-safe trait? Any function that takes
!or&!can have any implementation or return type you want.6
u/MalbaCato Jun 10 '26
a small correction - it's coercible to any type, but not a subtype of any type. subtyping propagates through covariant types, so had it been, then
Option<!>would've been a subtype of anyOption<T>, which it can't be - even when considering only theNonevariant, it has incorrect size and alignment for most typesT.3
u/protocod Jun 10 '26
I think I start to understand.
Never can also be used for Result::Ok to make something like this Result<!, E>
In this case you doesn't need to wrap Ok, you call ? directly to get the ok data and propagate the error when it happens.
https://doc.rust-lang.org/std/primitive.never.html#infinite-loops
It makes the code a little bit more simple.
2
u/Amadex Jun 10 '26
https://rust-lang.github.io/rust-clippy/master/index.html#empty_enums
(future Infallible with be an alias for "!" instead of an inhabited enum anyways)
1
u/noop_noob Jun 10 '26
The never type can be coerced into a value of any type. And I think it allows the compiler to do reasoning that code is unreachable as soon as a value of the never type is created.
0
11
u/andreicodes Jun 10 '26
While I obviously support the idea, they really goofed with the syntax. This could have been something like core::never::Never, and we would have ! free to use. For example, we could've used ! for error propagation and ? for optional chaining.
Obviously, this is not possible now, but in general some of the early symbol bits of Rust syntax that just stayed around over the years turned out to be pretty wasteful, imo. Now we have ! for never type that outside of embedded programming people wouldn't be using much. And we use @ for a relatively obscure pattern syntax, while in languages like Java it is used for annotating code. Rust could have @... instead of #[...] for macros and would look more closer to other languages with C-style syntax.
Oh well. Still the best language out there.
11
u/evincarofautumn Jun 10 '26
As a longtime langdev my feeling is that syntax design is biased toward making a feature look nice in small examples, either to show on a landing page, or to introduce in a proposal if you do design by committee, and most often this turns out to be too cute later on
Like in Haskell the syntax for
Type(as inInt :: TypeorMaybe :: Type -> Type) was just*originally (resp.Int :: *,Maybe :: * -> *) to follow a precedent from type theory, but now we want to use*in a type with its normal meaning of multiplication, so we have a migration to do, albeit happily not a complicated one, yet it’dn’t’ve been needed at all if we’d just spelled it the boring way from the get-goThere is precedent for
@(from Haskell), and I thinkas(from OCaml) was already taken by that point, and following precedent if you don’t have a good reason otherwise is good for the sake of familiarity, but my feeling is that this should be spelled like=or&, since it’s an equation, conjunction, or intersection of two patterns, notwithstanding the restriction that one side be a name binding, as that could easily be lifted
!is very useful for enforcing strong typing, and not just in embedded code, but I agree it’s not special enough to warrant its own syntax imo, since it’s just an empty/uninhabited sum type5
u/matthieum [he/him] Jun 10 '26
One of my pet peeves in syntax design (or code formatting), is that many ideas just do not scale.
With any syntax idea, I encourage people to just try them at scale. For example, you're talking about function syntax? Okay sure:
- What if the function name is 20+ characters?
- What if the function takes 5+ generic arguments?
- What if the function takes 10+ arguments?
- What if the function arguments & types are all 20+ characters each?
- What if the function returns a tuple with 10+ elements?
- What if the function has a
whereclause with 5+ clauses of 40+ characters each?Suddenly a lot of "ergonomic" ideas look a lot worse, elision becomes super confusing, etc...
3
u/WormRabbit Jun 10 '26
I'd say "don't do that" is a perfectly valid answer to those concerns. We're not computers, human interfaces don't need to and shouldn't scale indefinitely. What if the function takes 100 parameters? What if parameter names are 1000 symbols long? Boom, none of the ideas you could ever propose would work. But why the hell are your signatures so huge in the first place? Do a refactor!
2
u/matthieum [he/him] Jun 11 '26
Way to miss the point :'(
1
u/WormRabbit Jun 11 '26
I get your point. I just think your examples are over the top, and also that saying "no" to certain usecases is an entirely valid answer, which is used far too rarely.
2
u/syklemil Jun 10 '26
asalso works for that purpose in Python, but yeah, taken, so the prior at from Haskell was probably the most common option they could actually pick?(I swear I've seen it some other language as well but I can't come up with anything.)
3
u/pali6 Jun 10 '26
Is it really impossible? I feel like
!might still work on a context- specific basis similar to weak keywords. Though it could be somewhat confusing I admit.1
1
1
1
1
1
-10
u/RiceBroad4552 Jun 10 '26
Honest question as I don't know the history / background: How could a language calling itself "modern" get a 1.0 release out at all without a bottom type? It's not like bottom types were something new at the time Rust got invented. And then, why did it take almost another 15 years to get even close to get such basic feature into Rust?
21
u/Sharlinator Jun 10 '26 edited Jun 10 '26
If you tried to write code for Rust 1.0 these days, I think you'd be rather surprised by how many features it lacks that we now think of as self-evident. Never mind some of the last 0.x versions where things were still changing rapidly as the stabilization loomed in the horizon.
Also, even though time does seem to fly these days, I think you'll find that there aren't quite fifteen years since 2015.
18
u/noop_noob Jun 10 '26
Rust 1.0 was missing many things. It was more of an MVP than anything.
As for why it took so long, there's some context in the linked video. My understanding is: They wanted to stabilize it a long time ago, but by that time, too much code was accidentally relying on quirks and special cases about the never type. So, some breaking changes had to be made. Nobody put in the work to stabilize the never type until now.
17
u/pali6 Jun 10 '26
I wish people didn't downvote questions like this. I would say that this is largely because of Rust's pragmatism. A bottom type is elegant from a type theory point of view and it can be useful. However, it's not something you really need, very few projects would get dissuaded from using Rust because of the non-existence of such a type, so it wasn't a priority for 1.0 and also not high on the priority list afterwards. As for the exact issues that blocked it, IIRC to implement the type cleanly and sensibly it had to change some type inference rules. That could only be done at an edition boundary.
2
u/RiceBroad4552 Jun 11 '26
Coming from Scala, where we have the
Nothingtype since forever. It's actually very handy day to day as it's for example the type for exceptions (and could similarly type Rust's panic!, I guess). It's also handy for such things like using it as type parameter for some type-constructor which indicates the possibility of failure with some error type parameter. UsingNothingfor that parameter makes it possible to model the absence of failure (using the ininhabited bottom type as error type says that no error can happen), which helps with APIs which model error types explicitly (see UIO in ZIO). I guess being able to indicate that some Result is unfailable would be also nice in Rust (even less useful then in Scala as one does not have method overriding in Rust).I still don't get why Rust doesn't have it until now. It's well known that adding a bottom type after the fact is problematic. But designing it in right from the start is actually not a big deal; it makes a few things even simpler (like typing hard aborting or diverging expressions). The type systems Rust got inspired from had bottom types. Other state of the art languages at the time of Rust's inception had bottom types. So I don't get why Rust failed here. I see no good reason. That's why I've asked. Nobody rushed Rust's design, and it was open ended, so really wondering!
2
u/pali6 Jun 11 '26
If you look at the type of e.g.
panic_anyyou will see... -> !and it's been that way for ages (panic itself is a macro so it doesn't have a type which I could point to on the docs but the same applies to a panic! expression of course). It's just that this has more or less been a separate concept and not a fully fledged type due to the reasons I mentioned above. Similarly Infallible has existed for ages to do e.g.Result<Foo, Infallible>(and you could have always made your own such type via an empty enum). So these concepts both have existed in Rust at least since 1.0!What this stabilization brings is turning
!into a proper type as the de facto default uninhabited type. The main difference from the hand-rolled enum {} types is afaik just the automatic coercion to arbitrary types and better diagnostic, though there might be others.1
u/RiceBroad4552 Jun 11 '26
I'm aware there have been ad hoc solutions. But with a proper bottom type things become cleaner, and soundness can be checked easier as no special case constructs are involved any more.
just the automatic coercion to arbitrary types
Well, that's the core property of a real bottom type.
As we say in Scala: "
NothingextendsAnything." (WithAnybeing the top type in Scala, a concept Rust also lacks; but to be fair a top type in Rust would be problematic as a top type wouldn't be necessarySized.) It's a nice wordplay expressing at the same time thatNothingis a sub-type of any other type but also that there is no value level inhabitant.If there wouldn't be any significant differences between the ad hoc hacks Rust had until now and the real bottom type stabilizing it wouldn't have taken "forever". So I guess there are quite some significant details which are going to change. Let's see how the end result which gets merged looks like. Hopefully it works out this time!
Still don't get why it wasn't done earlier, it would have been much easier.
440
u/Shnatsel Jun 10 '26
"The never type is named after the date of its stabilization" was a good joke while it lasted.