Stabilizing Rust's Never Type

(lwn.net)

242 points | by cjd8 4 days ago

13 comments

  • Georgelemental 23 hours ago
    > For many years, the standard library has had an `Infallible` type to work around the unstable nature of the never type. It served the same semantic purpose as the never type, but did not have any special compiler support. Therefore, code using it would be technically correct but suboptimal (such as having an extra layer of tags in an enumeration or emitting dead code), because the optimizer would not always be able to remove references to `Infallible`.

    This is incorrect. The compiler has always treated `Infallible` as uninhabited, and used that fact for optimizations. The downside of its lack of compiler support is losing out on the coercions. (The article is excellent otherwise)

    • mattmcal 18 hours ago
      > (Note: Rust also uses exclamation marks to indicate calls to macros. The way the syntax is constructed, a place where it is valid to use the never type is not a valid place to put a macro invocation and vice versa.

      Yeah this statement is incorrect as well. Weird to have these minor technical inaccuracies in a relatively detailed article.

      • tyushk 16 hours ago
        Do you have an example? I'm struggling to think of such a case where ! is ambiguous between a end of macro call and the Never type.
        • Sharlin 15 hours ago
          Pedantically, the phrasing in the article is incorrect because macro invocations are permissible in type positions. But as you said, the macro invocation syntax is nevertheless not ambiguous with !-as-type (or !-as-operator for that matter).
    • tialaramex 19 hours ago
      Which makes sense because you could (and indeed still will be able to do) write your own uninhabited types very easily in Rust and indeed they're optimised accordingly. Because Rust has user-defined sum types you could simply write a sum of nothing:

          enum MyNeverType {}
      
      ...and it's uninhabited, the same way you can write the product of nothing

          struct MyUnitType {}
      
      ... and its size is zero.
      • nextaccountic 19 hours ago
        Note, to be more clear, an enum with no variants has no values of that type (can't be constructed), and a struct with no fields has exactly one value of that type
        • teiferer 13 hours ago
          Just like you would expect from algebra. The empty sum is 0, the empty product is 1.
        • aissi 11 hours ago
          [flagged]
          • nixpulvis 11 hours ago
            A reference to a value isn't the same as the value itself. I'm confused the point you're making here.
          • red75prime 9 hours ago
            > remember to kill all pedophile murderers like...

            What's this bullshit?

          • horseduck 11 hours ago
            tialaramex got it wrong even at the type theory level, a size of 0 is the wrong focus, the number of elements, 1, should have been the focus.
            • kibwen 10 hours ago
              I wouldn't say they got it wrong exactly. Uninhabited types aren't equivalent to unit types, but in terms of Rust they're both classified as zero-size, and I suspect they were just pointing out that Rust has optimized and special-cased zero-sized types accordingly since time immemorial.
  • srdjanr 12 hours ago
    I like the pragmatic approach to backwards compatibility (accepting relatively rare breakage that's not too hard to fix) instead of requiring 100% compatibility without exceptions
    • hahn-kev 10 hours ago
      Yeah this is good. I feel like a language which never fixes a mistake ends up stuck. JS by it's nature can't fix things like this and it sucks.

      Thankfully Rust is compiled so this break is at compile time, and seems simple to fix. I wonder if their could even be an auto fix for it.

    • geocar 11 hours ago
      I don’t.

      My software gets done.

      The idea that it would suddenly not be done because the compiler upgraded sounds like a potentially limitless amount of future work. Why would I want to invest in something that promises that?

      • baq 9 hours ago
        The answer is to keep the compiler and the OS image it is running on alongside the source in version control. (Docker alone won’t save you.)

        Unless you just have an axe to grind?

        • jcelerier 8 hours ago
          > The answer is to keep the compiler and the OS image it is running on alongside the source in version control.

          really says a lot that someone can say this apparently absolutely seriously

          • baq 8 hours ago
            I was just lucky to have found an ancient rhel box when I desperately needed one.

            Luck is not a process.

            • jcelerier 7 hours ago
              I mean I understand that this can happen, my point is that we should code things in a way where the huge majority your code will still work no matter if you have an old rhel box or the latest archlinux, just like today we can port doom to pretty much any platform without too much trouble.
        • irishcoffee 8 hours ago
          It’s very amusing to me that rust is trying to achieve a safety critical certification. Whatever version of the compiler that gets the cert will be cemented for the next 20 years.

          Safety critical work / security-sensitive work is completely orthogonal to the rust release cycle.

          Not to mention the vast amount of professionals who don’t have root on their runners / build nodes. Putting a ticket in every 3-6 weeks for a new compiler version takes… 3-6 weeks. People give up after a while.

          • estebank 7 hours ago
            > Not to mention the vast amount of professionals who don’t have root on their runners / build nodes. Putting a ticket in every 3-6 weeks for a new compiler version takes… 3-6 weeks. People give up after a while.

            When I talked with the people at AWS responsible for updating both the myriad Java compilers in use as well as Rust for every project in the company, they claimed that updating Rust was always painless and didn't even appear as a blip in their radars compared to other similarly wide reaching updates.

            • irishcoffee 7 hours ago
              You’re mistaking a technical problem with a process problem.
      • fl0ki 9 hours ago
        This isn't the only way a Rust stable update can break your compile. It can also happen simply because they add a symbol to the standard library, and that isn't even protected by language editions.

        https://predr.ag/blog/some-rust-breaking-changes-do-not-requ...

        "Never add anything" isn't a tenable position, and in practice the breakage hasn't been bad enough to need special treatment yet.

        • brabel 6 hours ago
          Oh not only to the stdlib but to any lib! I thought that was crazy when you mentioned it but after reading the post I agree it’s a pragmatic approach. By the way Java does the same! The problem can be avoided by avoiding “star imports” in both Java and Rust, and at least in Java star imports are very rarely used for this exact reason (I remember my horror when I couldn’t call a method of List that I knew existed and it turned out a “import java.awt.*;” was the reason, that was a pain to figure out before AI and convinced me to never use star imports again).
          • fl0ki 4 hours ago
            Star imports aren't the only way, traits are another. Say you impl two traits and they end up both adding a symbol with the same name. Or you bring in an extension trait impl for a standard library type, and now the standard library adds a symbol with the same name as one in the extension trait.

            To avoid those problems, you'd have to proactively disambiguate any item with its trait name, which is so un-ergonomic that people genuinely prefer the possibility of occasional breakage.

      • pseudocomposer 10 hours ago
        I agree with your sentiment, but at some point backwards compatibility has to break. Rust handles it better than basically anything out there. If it bothers you, you should never try any other language except maybe plain, no-framework JS.

        Plus, LLMs have really made upgrading a codebase for a compiler or dependency update into a trivial chore, at least for the most part.

      • aiono 9 hours ago
        Because software is rarely done. Also you will likely to write newer programs with the same but improved language that doesn't have the warts in the earlier versions. Isn't it worth paying a small price for the improvement you get in the future?
      • nixpulvis 11 hours ago
        You could keep using the old compiler, no?
        • Ygg2 11 hours ago
          Yes, but new compiler can't use old ecosystem crates. Hence the issue.

          Rust's edition system gives you the best of both worlds, with caveats. Some changes will be impossible.

      • psd1 10 hours ago
        Everything is a trade-off. Would you never ever under any circumstances accept even trivial breakage, even if it fixes a horrible wart that costs thousands of lost hours?
      • slopinthebag 7 hours ago
        Your software is done, so why do you need to recompile it with a new version of the compiler? After all it’s done. Pin compiler version and problem solved. No limitless amount of future work.
        • jstimpfle 7 hours ago
          For the most part, software does not exist in a vacuum. If there is a serious bug, or much improved feature, in a library you depend on, what do you do? Most likely you'll try to upgrade the library. Same deal for the compiler, it's a huge dependency.

          If your project is truly done, by all means ship it on a N64 cartridge. I can't say I don't have sympathy for that attitude. But don't expect this is how the world works today. And don't forget, N64 cartridges weren't built using massive amounts of dependency, so it was easier to confidently declare something "done".

          • slopinthebag 7 hours ago
            Ok but if you have to update your software it’s not done, it’s dormant.

            And when it wakes up, it’s not like this is some massive change. It’s trivial and automatable.

      • Analemma_ 9 hours ago
        It’s not actually true that your software gets done, that’s essentially impossible unless you’re doing some kind of performance art project targeting a defunct platform from decades ago. Operating systems and libraries change underneath you all the time, even if you’re just using Linux and glibc, and if you’re not keeping up eventually your software is the legacy code keeping people stuck on an insecure OS, like those businesses who have to keep one box running DOS because of some ancient device driver for their equipment.

        It’s better to plan for this in advance and use a stack where upgrades are done gracefully, rather than sticking your head in the sand and pretending it doesn’t happen.

    • Ygg2 11 hours ago
      Like Python 2 vs 3? That's what Editions were created to fix. With backwards incompatibility you will get ecosystem breakage and looming threat of future compilers not compiling your code.
      • estebank 7 hours ago
        The problem is that assuring no breaks ever with inference means you can't ever improve the inference algorithm nor update the stdlib. This is what triggered the time 0.35 breakage. I have a still incomplete/unmerged rustc lint to avoid the situation that caused that (a useless .into() that didn't get flagged because the clippy lint has false positives so it is not on by default) which should minimize the likelihood of that happening again (once I get off my ass and finish it, it just requires some side-quests to add more accurate tracking of cfg'd out items). This might be able to be mitigated by editions, but in practice crater helps to not need that (yet?).

        This is not the only kind of breakage a project can experience. Trying to bring up an older project on a new platform will be a compile error (like building a project from 2018 on an Mx Mac), and updating the appropriate dependency to an appropriate version might become a chore. I have no idea how to improve the situation there to make that less painful, beyond having a simple database of crate-version+platform+rustc-version so that the toolchain could provide better/actionable messaging beyond "shit's broken".

  • cipherjim 23 hours ago
    My favourite never type ability is when you need to conform to a trait that returns Result but your specific implementation can never produce an error.

    Return Result<T, !> and the compiler knows that callers never have to check the error case because by definition it can’t be constructed.

    • pascahousut 14 hours ago
      My first encounter will likely be the opposite, i.e. a call that will only produce a Result::Err if anything, and never a Result:Ok. I've made programs where there are a bunch of continuously running jobs that should never terminate in the happy scenario. If any of these jobs terminate, it'll be due to some sort of failure, and so Result<!, MyError> seems like a reasonable return type for such job. I think I've already used the Infallible in there but I think this being part of the language now makes the code feel more correct or clean or something like that.
    • tialaramex 19 hours ago
      And if your callers are generic over the error type, and so they do have code for handling errors, the compiler won't emit this code for your result type because you've said its error type can't exist.
      • tyre 17 hours ago
        Does that mean you can assign the result of the function without explicitly unwrapping? Or that the compiler removes the call to unwrap? Or neither?
        • bonzini 16 hours ago
          You need to unwrap but the compiler removes the call. If instead you know that the callee is infallible you can also do

              let Ok(x) = call_that_cant_fail();
  • LatticeAnimal 1 day ago
    Is it obvious to rust developers that "!" would be the never type? I frequently use "never" in typescript. I could imagine using the never type frequently in rust too. I feel like a longer more human-understandable name would've been a good decision here. (feels like more rust jargon that makes the language harder to learn)
    • sheept 23 hours ago
      Even though the never type has been experimental for a while, I've seen `!` used in the docs,[0] so at this point, Rust developers are probably already aware of what it means even if they haven't used it before.

      [0]: Example: std::process::exit returns `!` https://doc.rust-lang.org/1.0.0/std/process/fn.exit.html

    • p1necone 23 hours ago
      Yeah I think just calling it 'Never' is much clearer, this isn't something that needs a dedicated single character, and ! is less readable imo
      • nielsbot 16 hours ago
        Agree. This is also what Swift uses.
    • amomchilov 1 day ago
      Yeah it really baffles me why a symbol like `!` was spent on this, which could be more useful for more a more commonly used feature.

      I just checked, my main side project only has less than 10 things that return never. `-> Never` reads even better, imo.

      • kibwen 1 day ago
        The exclamation point is also used both as the C-style negation operator and as the identifier suffix that indicates macro invocations, so it was unlikely that it would have been used for any new feature. As my sibling comment notes, this syntax for divergence is very, very old (predating even 0.1), not something that anyone newly came up with.

        And if you'd like to write `-> Never`, the nice thing about being a first-class type is that you can now just do that if you'd like, via a standard type alias: `type Never = !;`.

    • hahn-kev 10 hours ago
      Yeah, I don't like it when languages use too many symbols for things. It's a hard line as I don't like languages where everything is a keyword (e.g. begin end vs { }), but not enough keywords and it's hard to learn and remember the language.
    • kibwen 1 day ago
      > Is it obvious to rust developers that "!" would be the never type?

      Prior to this change most Rust developers would never have cause to ever use `!` for any reason. The only stable way to do so would be to specify the quote-unquote "return type" of divergent functions, which Rust has supported via this special-cased syntax since prehistoric days, before even Mozilla got involved. You can see it in the oldest capture of the tutorial from Jan 2012: https://web.archive.org/web/20120109041112/http://www.rust-l...

      So when it came time to elevate `!` from being a special-cased return type to being a fully-fledged type, it was only natural to reuse this syntax. However, I tend to agree that, because we call it "the never type" in casual conversation, the most natural thing to do would be to just have a type alias called `Never` that we could encourage people to use instead. But that would be a perfectly backwards-compatible change that could be made at any point (as proven by the fact that the stopgap and long-stable `Infallible` type is becoming just such a type).

      • ketzu 17 hours ago
        > Prior to this change most Rust developers would never have cause to ever use `!` for any reason.

        As a type.

        "!" is in the most basic example nearly every rust developer has seen:

            println!("Hello, World!");
        
        Unless you specifically watched a presentation or read a blog post about the never type, you probably haven't seen it as a type.

        If the average (and nearly all new) rust developers encounters "fn bla(blub: i64) -> !" I suspect they will mostly go "huh??" (or wonder what kind of weird macro that is) until it becomes common to encounter early and gets it's own early entry in the rust book.

        Compared to reading "fn blub(bla: &str) -> Never", which seems rather straight forward imo. However, "Never" might be the name of some actual Struct or Enum in various codebases.

        • simonask 13 hours ago
          Well, one thing that every Rust programmer knows is the `panic!(...)` macro, and its return type is "!".

          That's why this compiles, even though not even match arm produces a value, which I think many Rust programmers are familiar with:

              let b = match a {
                  0 => 123,
                  2 => panic!("mustn't be two"),
                  _ => todo!(),
              };
          
          Other expression that are evaluated as having the type "!": return, break, continue.
  • xg15 1 day ago
    > After this change (and on the 2024 edition), the compiler assumes that T should be !, which doesn't implement Default, and therefore causes a compilation error.

    If ! can coerce to every type, why not treat it as if it implemented every trait too?

    • tux3 1 day ago
      The Default trait provides a function that actually constructs the type in question. But here the ! type can never be constructed, so the only way to implement Default would be to have it panic, loop infinitely, or otherwise fail at runtime.

      So this would risk turning a compile-time error into a runtime error.

      • dlubarov 1 day ago
        Moreover, Rust traits' associated constants/types get in the way of having a proper bottom type. What would <! as Iterator>::Item be? (In Scala I think it just doesn't compile?)
        • SkiFire13 1 day ago
          Not only that, if you have `trait Foo: Iterator<Item = u8>` and `trait Bar: Iterator<Item = i32>` how could `!` implement both of them? It would simply make the language incoherent.
        • 10000truths 1 day ago
          > What would <! as Iterator>::Item be?

          Another ! makes sense to me here. Are there any cases where it doesn't work to auto-assign ! to all associated types of a ! trait impl? Associated constants might require some mechanism similar to `compile_error!()`.

      • xg15 1 day ago
        Ah, that makes sense. Rust noob here, so I wasn't aware traits can act on types directly without any instance of the type. Thanks for the info!
        • Sharlin 1 day ago
          Yep, they can have static methods, as it were (in Rust lingo called "associated functions"; "methods" in Rust always take a `self` receiver). Traits can also have associated types and associated constants, which (naturally) also relate to the type, not any particular instance.
  • weinzierl 1 day ago
    Relevant talk by Waffle at RustWeek earlier this year:

    "When is never?"

    https://youtube.com/watch?v=3jM4cnEVrLc

  • salsa_catsup 21 hours ago
    Is this so central, that it justifies the use of a single ascii char? Instead of, say, `Never`?
    • simonask 13 hours ago
      It's not really like there's a budget for it, and "!" is already a type people have seen in the return position of extremely common macros (panic!, todo!, etc.), functions (std::process::exit, etc.), and expressions (return, break, continue).

      The question is more whether it's unambiguous (it is), and whether there's something else you would rather use it for (there isn't).

    • ddosmax556 10 hours ago
      It results in different behavior in the compiler. You don't have to check the return type of a function in which you call a function return never. Using a different symbol gives you a hint why the code works, if you used `Never` you'd have to KNOW it works differently.
    • pie_flavor 11 hours ago
      It has been in the language for fourteen years. What would be the purpose of changing it? And what else would ! in the type position signify?
    • db48x 16 hours ago
      I can see that you’ve never designed a language. The design and implementation of Rust took many years. Over that time people’s ideas and priorities changed. Even the people changed. In the early days of the language design sigils were very heavily used throughout the language. Even the language keywords were deliberately shortened as much as possible (consider ”fn“ and ”ret“, for example). Most of the sigils were eventually replaced with traits, but not all of them. ”!“ is one that survived.
  • kccqzy 1 day ago
    The lesson here is that implicit conversions are bad. The never type itself having implicit conversions to other types is bad enough (even though such coercions are logically valid: “ex falso quodlibet” they should be explicit), but having a fallback type when type inference doesn’t have enough information to produce a type is even worse. Rust is famous for not even having implicit numeric coercions (say from i8 to i32) but it seems like a shortsighted decision to allow implicit coercions here.
    • jadenPete 1 day ago
      Why is it bad? Implicit integer conversions are generally bad because they can produce unexpected behavior at runtime and obstruct what’s really happening, but that doesn’t seem to be what’s happening here.

      Never is a standard type in many languages and is at the bottom of the type hierarchy because it’s a subtype of every type. Never isn’t implicitly converted any more than `&’a A` is “implicitly converted” into a `&’b B`, where `’a` subsumes `’b`. There’s no runtime conversion because there will never be an instance of never—it represents the value of a computation that never completes by definition.

      I think what you mean to say is that implicit runtime conversions are bad, not that all subtyping is bad.

      • kccqzy 1 day ago
        No I’m not talking about runtime conversions. I’m talking about conversions that happen at type inference time.

        Rust is not a subtyping based language, except for traits and lifetimes. So statements like never being at the bottom of the type hierarchy is irrelevant here even though it is correct. If Rust had higher rank types the never type is also (forall a. a) but still it doesn’t matter. It is simply surprising for a type to be converted implicitly according to subtyping rules other than for traits and lifetimes.

        • SabrinaJewson 22 hours ago
          Do you have an example of a piece of code that behaves in a surprising way because of this rule?
          • kccqzy 22 hours ago
            I don’t need to write examples because the article has plenty. All the fixes that Waffle needs to fix are precisely the code that behaves in a surprising way.
            • kibwen 20 hours ago
              None of this has anything bad to say about coercion or fallback, it's a consequence of the fact that Rust is an expression-oriented language which had expressions (like `loop {}`) which logically evaluated to the never type when in return position, and yet did not have the machinery in place to support it as a proper concept anywhere outside of return position, and so they chose the unit type as a relatively benign alternative in those contexts, which caused no problems whatsoever until the day came when they decided to actually implement the never type.
              • kccqzy 12 hours ago
                > so they chose the unit type as a relatively benign alternative in those contexts

                This is what the article defines as fallback. So the issue has everything to do with fallback.

        • kibwen 22 hours ago
          Let's avoid using the term "subtyping", which as you say is irrelevant here. The reason you need diverging functions to satisfy arbitrary type obligations (i.e. to coerce to any other type) is because otherwise anything as simple as `let x = Some(42); x.unwrap();` just completely fails to compile, because `unwrap` is internally just:

              fn unwrap<T>(t: Option<T>) -> T {
                  match t {
                      Some(foo) => foo,
                      None => panic!()
                  }
              }
          
          ...and this function couldn't otherwise typecheck because it doesn't return a `T` in the `None` branch. You need coercion here.
          • kccqzy 22 hours ago
            No you don’t need coercion. You only need polymorphism. The type of `panic!()` could be an arbitrary U, which unifies just fine with the type T here.

            Generally languages with such polymorphism have a never type only because they don’t also support impredicative polymorphism.

            • kibwen 20 hours ago
              And then once you have `fn foo<T>() -> T`, what do you write in the body that allows it to typecheck?
              • kccqzy 12 hours ago
                You still write `panic!()`. It type checks using polymorphism only, without any coercions.
                • kibwen 9 hours ago
                  But `panic!()` is just a macro invocation that needs to expand to something, and the question here is what that something ought to be in order to produce a valid program. Currently it expands to this: https://github.com/rust-lang/rust/blob/98fd715edd3a0a5aa8f20... , which is a function with a return type of `!`, which has indicated a diverging function since long before Rust even considered having a first-class never type.
                  • kccqzy 7 hours ago
                    Right. And the whole discussion boils down to, why can’t this function return type be polymorphic in the first place? This avoids the never type, the coercions from the never type, and all the issues caused by that described in the article.
      • SabrinaJewson 22 hours ago
        You’re using “subtype” in two distinct, but related, senses here, and I think this should be clarified.

        From a more category-theoretic perspective, a type A is a “subtype” of a type B when there is an embedding of A inside B. In this sense, `!` is a subtype of every type (which is its universal property). But this definition also grants you that `String` is a subtype of `BigInt`, because strings can be coded as bit sequences which can be coded in `BigInt`, which may or may not be what you expect.

        From a programming languages perspective – and this is the terminology generally used in Rust – a type A is a “subtype” of a type B when `a: A` implies that `a: B`. In this sense, `!` is only a subtype of itself; although it coerces to any other type, it’s not _literally_ of that type, the coercion is just invisible in syntax. Importantly, if A is a subtype of B then `Vec<A>` is a subtype of `Vec<B>` – but `Vec<!>` is definitely not a subtype of `Vec<T>`, since they may have totally different layouts in memory (the former not allocating at all, while the latter potentially allocating).

        • kccqzy 22 hours ago
          > A is a subtype of B then `Vec<A>` is a subtype of `Vec<B>`

          That’s just not true. Java would permit it but then you get ArrayStoreException so this is unsound from a type system perspective. To make this sound, we need to classify each use of a type parameter to be covariant, contravariant, or invariant.

          • jkhdigital 19 hours ago
            Java doesn’t permit that. You must specify covariant or contravariant type parameters with <? extends T> or <? super T>.
            • kccqzy 12 hours ago
              That doesn’t apply to plain old arrays, which were in the language before the designers actually collaborated with type theory experts.
          • i2talics 18 hours ago
            You are misled for two reasons.

            First of all, Rust isn't subject to the same soundness issue as Java precisely because of the Rust's ownership semantics. You can't produce the ArrayStoreException issue because you can't mutably alias a Vec in the first place. To be more precise, &mut T is invariant, but Vec<T> is covariant (in T).

            Second of all, Rust already does classify the co/contravariant status of all of type parameters. If you've ever tried to omit a type parameter from the fields of a struct and find that you're forced to insert a "PhantomData" value, this is because the entire purpose of PhantomData is to imply what variance classification the compiler should give the type parameter.

    • cipherjim 23 hours ago
      With ! the implicit conversion happens at compile time, never at runtime.

      It cannot by definition happen at runtime because the never type has no values and thus cannot be constructed under any circumstances.

      Any compile time coercions that occur would convert types (or generics args of types) to !

      I find it difficult to imagine any situation where that would result in a working program - only if the coerced types or references to coerced generics were not even used would it compile.

    • echelon 1 day ago
      We should be able to set at a crate level whether our code can compile with panics, implicit conversions, etc. And we should be able to blacklist dependencies and transitive dependencies that do these things. We should be able to advertise a crate's safety and attention to detail.

      Higher level application code can benefit from this, but core libraries should forbid this statically and be prevented from even compiling or being imported should these things be enabled.

      We should be able to filter crates.io by these properties, and force our own projects to abide by them.

      I want nopanic, nocoerscion, maxdependencydepth, rustonly, nolinking, etc. flags.

      • kibwen 1 day ago
        Do you have some specific coercion in mind that you want to forbid? Unlike C, Rust is extremely tame when it comes to coercions. Forbidding coercions in general in Rust code doesn't really make sense, and I can't think of any that aren't either beneficial at best or benign at worst.
  • munchler 1 day ago
    As a fan of the Curry-Howard correspondence, I approve of this decision.
    • SabrinaJewson 22 hours ago
      You’re going to hate when you learn that the never type is inhabited
  • epolanski 1 day ago
    The never type seems very useful in various languages to either signal that a branch can never happen (the example of string -> bytestring never erroring) or to mark that a function will never return a value (and thus control) to the caller.

    A simple TypeScript example:

    const forever = (): never => { while (true) { // whatever } }

  • kevinbaiv 23 hours ago
    [dead]
  • kelvo_ran 11 hours ago
    [dead]
  • HNBeLike 22 hours ago
    Sounds serious. Armageddon serious.

    Nobody should get ahold of this technology.

    Shut down the schools!

    Get rid of all small business (to mitigate the risk).

    15 days to prevent Never from destabilizing!

    We’re all in this together.