I stabilized never type
137 points by ettolrach
137 points by ettolrach
For the confused: https://doc.rust-lang.org/std/primitive.never.html
This is a surprisingly simple to understand introduction for why the never type is/(would be) a good addition for rust. Thank you for posting the reference.
I love the Rust docs. I have recently been doing more C++, and every trip into the stdlib docs makes me miss Rust
Too bad that they didn't follow the common convention and call it the "bottom" type, because that's what it's called in most other language to my knowledge.
And it's good that Rust has that type. Any language with a decent typesystem must have it. If a language doesn't have it, well, from my perspective it's an amateur language and a world of pain awaits you. Looking at golang for example...
Typescript and Python call it “never” and are two of the most popular languages in the world.
I’d say “bottom” is more a PLT term than industrial-language term, myself.
It's okay to name the type as never (or !) - just in the explanation it should refer to it as bottom type or ⊥ type. Rather than repeating typescript's mistakes. Python btw. does it right.
Sorry, I am a little lost. Are we talking about what we call it, or how it gets described in the official documentation?
Or, in the implementation of tsc, is never in particular a more unsound representation of bottom than the rest of the type system?
IMO it would be quite strange to call it "bottom", given that its dual (the type we spell () in Rust) is called "unit".
"top" + "bottom" makes sense, but "unit" + "bottom" seems inconsistent.
My first choice would have been calling it "empty". "unit" + "empty" would be consistent, and entirely within conventions. But too many people worried that "empty" would be misunderstood (i.e. many people think that void in C is "empty"). So, "never" it is.
Do you have examples of languages (and I don't mean academic theory languages, but ones that have actual use) that call it "bottom"? What do they call the "unit" type?
Wikipedia says that “never” is a more specific term for when the bottom type is uninhabited, which is an important property of Rust’s Never. Is it really incorrect? It’s definitely a more intuitive and useful name.
https://en.wikipedia.org/wiki/Bottom_type#Relation_with_the_empty_type
I didn't say that it's incorrect, I said it's unfortunate that they didn't follow the convention.
What I just wanted to say (but apparently failed to convey) is that they should actually mention that this is the bottom type - unless the naming and design is pure coincidence and it's not supposed to be the bottom type. But otherwise, calling it that and linking to Wikipedia would be nice.
Being pedantic we would not call this the bottom type. Rust doesn't have subtyping, it only has coercion. And coercion is limited, even for the never type. It's possible to write functions which will fail to compile if their bodies evaluate to never.
error[E0277]: `!` is not an iterator
--> src/lib.rs:1:17
|
1 | fn example() -> impl Iterator {
| ^^^^^^^^^^^^^ `!` is not an iterator
2 | loop {}
| ------- return type was inferred to be `!` here
|
= help: the trait `Iterator` is not implemented for `!`
To continue to be pedantic for fun; Rust absolutely has subtyping, but only for references: https://doc.rust-lang.org/nomicon/subtyping.html
Adding on more pedantry: No, not just for references. Any generic with a covariant or contravariant lifetime or type parameter generates types with a subtyping relation between them. For example, if 'a : 'b, then PhantomData<&'a> is a subtype of PhantomData<&'b>, but neither is or contains a reference (that's what PhantomData does). This allows you to have variance and lifetime subtyping on custom types that sorta work like references but aren't.
But yes, Rust's subtype relation is pretty sparse and does not have a bottom element.
Changing this is a simple matter of a PR adding the docs. I'm pretty sure such a PR would be welcome.
I can't think of any language that actually calls its bottom type bottom? Haskell calls it Void... I guess?, Typescript calls it never, Python calls it never....
Wikipedia's list has zero instances, if I'm reading it right https://en.wikipedia.org/wiki/Bottom_type
Strictly speaking, it's not a bottom type, and therefore calling it such would be incorrect.
(a bottom type is a subtype of all other types, whereas never is a type that coerces to all other types)
Rust places very high value on not breaking crates in crates.io. Normally, the ability to mix different editions of Rust simplifies this. But there are some deeper changes that editions can't help with. And Rust has a reached a point where many of those changes are difficult or perhaps impossible.
The most impressive part of supporting never is figuring out how to finesse the compatibility issues within Rust's backwards compatibility goals (and presumably convincing everyone you succeeded). Huge congratulations on doing lots of tricky work to get this done.
I was surprised to read the linked bug, which says "sets the never type fallback to ! on all editions (breaking change, [...]". That is to say, this change can break old code despite the edition system?
Writing this comment made me look harder at the bug (I realized I hadn't expanded the hidden comments) so there is more discussion there of why this approach was chosen. It looks like they previously landed a warning detecting the code that this change will break (?).
It's really hard to totally avoid breaking changes, if only because existing code may exploit bugs. One of the things the Rust project has done in the past is use their "Crater" tool to recompile all of crates.io and look for regressions. They have been known to send PRs! In practice, this means I've personally seen maybe 3 minor cases of technical breakage since 2015, all fixed in about 15 minutes on my end. Even coming from a C background, Rust's backwards compatibility often feels unreal.
I don't know how "never" fits into the Rust backward compatibility goals in detail, and if they're accepting a certain level of breakage. I just know that this issue had been blocked for ages, waiting for someone to figure out the corner cases and get consensus behind an approach. I am complimenting them not because I understand exactly which solution they chose, but rather because I know the problem was miserably hard the last time I looked at the discussions.
Yes, the fallback change can break existing code, and the edition system is of no help here.
The issue is a bit hard to explain (I've spend years trying to figure everything out ^^'), but the TL;DR is that we wanted to change Infallible to be a type alias to !, which creates an unwinnable scenario:
Infallible in some specific ways would break (because fallback to () makes the type "spontaneously" decay to () sometimes, instead of being kept as-is)Note that edition system can only do "syntactic" changes, as-in: "if I write some code, what does it mean/desugar to?", but it can't do "global" changes. In this case the problem is with Infallible, which has to be the same type independently of the edition.
We eventually agreed that
(there are also some more obscure problems which we added temporary hacks for, but those are a lot more minor)
You can learn some more from my RustWeek "when is never?" talk, or I guess by doing github archeology, although the latter is a big waste of time ^^'
Can you give some examples of the issues that were addressed?
I clicked through to Waffle's (incredible!) talk this morning, and understand the breaking-issues to have been a long tail of "last 20%" interactions with other features, conventions, and semantics (explicit or implicit) around things like unimplemented, the Unit type, and related equivalences, (esp. where they affect the semantics of a type signature). In practice most crates depend on affected packages, but the number of "roots" were tractable (a couple dozen?) and volunteered or merged fixes. I'm surprised about that, and want to know more too -- we can find gnarly crate-by-crate details in (and linked outed from ;u;) the tracking issue (github). A great overview of related links can be found in the body of the merged PR.
Just echoing both the sentiment and the thanks…
That is a Herculean effort that will have an enormous impact on a vast swath of computing across and around the world, for many, many years to come.
You are one of the giants whose shoulders others stand on.
This is amazing, thanks a lot to everyone involved!
So do we have to rename the type to "1.100" now? ;)