Mark Rousskov
    • Create new note
    • Create a note from template
      • Sharing URL Link copied
      • /edit
      • View mode
        • Edit mode
        • View mode
        • Book mode
        • Slide mode
        Edit mode View mode Book mode Slide mode
      • Customize slides
      • Note Permission
      • Read
        • Only me
        • Signed-in users
        • Everyone
        Only me Signed-in users Everyone
      • Write
        • Only me
        • Signed-in users
        • Everyone
        Only me Signed-in users Everyone
      • Engagement control Commenting, Suggest edit, Emoji Reply
    • Invite by email
      Invitee

      This note has no invitees

    • Publish Note

      Share your work with the world Congratulations! 🎉 Your note is out in the world Publish Note

      Your note will be visible on your profile and discoverable by anyone.
      Your note is now live.
      This note is visible on your profile and discoverable online.
      Everyone on the web can find and read all notes of this public team.
      See published notes
      Unpublish note
      Please check the box to agree to the Community Guidelines.
      View profile
    • Commenting
      Permission
      Disabled Forbidden Owners Signed-in users Everyone
    • Enable
    • Permission
      • Forbidden
      • Owners
      • Signed-in users
      • Everyone
    • Suggest edit
      Permission
      Disabled Forbidden Owners Signed-in users Everyone
    • Enable
    • Permission
      • Forbidden
      • Owners
      • Signed-in users
    • Emoji Reply
    • Enable
    • Versions and GitHub Sync
    • Note settings
    • Note Insights New
    • Engagement control
    • Transfer ownership
    • Delete this note
    • Save as template
    • Insert from template
    • Import from
      • Dropbox
      • Google Drive
      • Gist
      • Clipboard
    • Export to
      • Dropbox
      • Google Drive
      • Gist
    • Download
      • Markdown
      • HTML
      • Raw HTML
Menu Note settings Note Insights Versions and GitHub Sync Sharing URL Create Help
Create Create new note Create a note from template
Menu
Options
Engagement control Transfer ownership Delete this note
Import from
Dropbox Google Drive Gist Clipboard
Export to
Dropbox Google Drive Gist
Download
Markdown HTML Raw HTML
Back
Sharing URL Link copied
/edit
View mode
  • Edit mode
  • View mode
  • Book mode
  • Slide mode
Edit mode View mode Book mode Slide mode
Customize slides
Note Permission
Read
Only me
  • Only me
  • Signed-in users
  • Everyone
Only me Signed-in users Everyone
Write
Only me
  • Only me
  • Signed-in users
  • Everyone
Only me Signed-in users Everyone
Engagement control Commenting, Suggest edit, Emoji Reply
  • Invite by email
    Invitee

    This note has no invitees

  • Publish Note

    Share your work with the world Congratulations! 🎉 Your note is out in the world Publish Note

    Your note will be visible on your profile and discoverable by anyone.
    Your note is now live.
    This note is visible on your profile and discoverable online.
    Everyone on the web can find and read all notes of this public team.
    See published notes
    Unpublish note
    Please check the box to agree to the Community Guidelines.
    View profile
    Engagement control
    Commenting
    Permission
    Disabled Forbidden Owners Signed-in users Everyone
    Enable
    Permission
    • Forbidden
    • Owners
    • Signed-in users
    • Everyone
    Suggest edit
    Permission
    Disabled Forbidden Owners Signed-in users Everyone
    Enable
    Permission
    • Forbidden
    • Owners
    • Signed-in users
    Emoji Reply
    Enable
    Import from Dropbox Google Drive Gist Clipboard
       Owned this note    Owned this note      
    Published Linked with GitHub
    • Any changes
      Be notified of any changes
    • Mention me
      Be notified of mention me
    • Unsubscribe
    # T-lang design meeting: Never type 2022-02-23 ## High level overview There are three main components to the general never type feature: * Coercion to arbitrary types (`!` can become something else) * Changes to inference fallback (unconstrained variables become `!`) * `std::convert::Infallible` aliasing ## Coercion to arbitrary types **Stable behavior**: functions with `!` return type, abrupt expressions (return, loop, etc.). This is what allows expressions like panic!() in contexts that expect some other type, and is generally fairly widely used. The main stability implication from this is that `!` is easy to 'lose': any coercion site which does not constrain to specifically `!` will end up using the inference fallback (see next section), which may result in a different type from `!`. ## Changes to inference fallback **Unstable behavior**: gated on `never_type_fallback`. Rust's current inference rules have a default fallback of `()` for unknown inference variables (that are not from integer/floating point literals, which are separately treated) which aren't constrained to any specific type. This means that any `?T` which has no direct constraints placed on it will become `()` on stable today. This is typically not noticeable, since unconstrained inference variables are somewhat rare in live code. The principle behind changing fallback from `()` to `!` is that since the variable is constrained, the type is 'unobserved' and so the expression must be dead. In practice, there are several cases where this is not true; some examples follow. Note that changing a variable with type `()` to `!` can lead to UB, as the latter is uninhabited. A sample of code where today we fallback to `()` in live code: ```rust let x: /*?X*/ = match something() { pattern1 => Default::default(), // has type `?T` pattern2 => panic!("..."), // has type `!` }; // the match has type `?M` ``` [playground](https://play.rust-lang.org/?version=nightly&mode=debug&edition=2021&gist=f74310b43955cdf58067f656bfcc53dd) -- Use "Show MIR". `?T` is not constrained, so it falls back to `()` on stable today. There are an assortment of similar cases (in spirit if not in exact syntax in [this issue](https://github.com/rust-lang/rust/issues/66173)). It is perhaps worthwhile to note that at least in Mark's opinion the code here likely should never have compiled, instead demanding an explicit type be provided. ## `std::convert::Infallible` aliasing `enum Infallible {}` was stabilized in order to permit moving forward with `TryFrom` alongside a blanket impl for infallibly convertible types (i.e., those with a `From` impl). The original intent was to move `Infallible` to be an alias for `!`, rather than just an empty enum. There is no intrinsic benefit to doing so, though, beyond slightly easier compatibility with APIs returning `!` (mostly by avoiding the need for explicit coercion sites) and shorter typing for APIs dealing with infallibility. # Interactions & Problems ## Coercion to arbitrary types The ability for `!` to coerce to arbitrary types can lead to confusing/unexpected behavior for users. This is partially mitigated by the inference fallback change to `!`, which means that there are fewer cases where an API expecting `!` happens to see `()`. ```rust= fn magic<R, F: FnOnce() -> R>(f: F) -> F { f } fn main() { let f1 = magic(|| {}) as fn() -> (); // works fine let f2 = magic(|| loop {}) as fn() -> !; // errors } ``` This [example](https://github.com/rust-lang/rust/issues/66738#issuecomment-1004381083) will compile with never_type_fallback enabled, but otherwise fails, even though it *seems* like everything should work since `loop {}` has type `!`. However, the return type of a closure here is unconstrained during inference, so it falls back to `()` on stable today. ## Changes to inference fallback In order to backwards compatibly land `type Infallible = !`, we need to change inference fallback to coerce to `!`, as otherwise stable code which tries to produce `Infallible` will instead result in `()` (similar to the above case with coercions), resulting in breakage. A few standard examples are found on [this page](https://rust-lang.github.io/never-type-initiative/evaluation/no-inference-changes.html). However, due to soundness concerns (`!` appearing in live code means UB), we need to be careful about enabling fallback to `()` in all code. This leads to a relatively complicated design for the fallback algorithm: soundness along is *probably* (relatively high confidence, but no certainty I believe) is achieved with [v1](https://rust-lang.github.io/never-type-initiative/explainer/conditional-fallback-v1.html) design. This design constructs a coercion graph to try and detect which inference variables interact with live code and selects whether to fallback to `!` or `()` based on that. As noted there, this is insufficient to avoid breaking changes. Crater finds roughly ~100 crates broken by v1 design, including some notable regressions in wasm-related crates. [v2](https://github.com/rust-lang/rust/pull/88804) of the fallback algorithm slightly expands v1 to put a few extra cases to `()` fallback, preventing ~70 of those crates from regressing, including the notable crates. This version adds to v1 one more rule: * `?T: Foo` and `Bar::Baz = ?T` and `(): Foo`, then fallback to `()` Obviously, this is pretty ad-hoc, but is sufficient to fix code like this: ```rust= trait Foo { } impl Foo for () { } impl Foo for i32 { } fn gimme<F: Foo>(f: impl Fn() -> F) { } fn main() { gimme(|| panic!()); } ``` We don't fix a case like this, though; this occurs more rarely in the ecosystem and fixing it has some unfortunate implications for actually using `!` (many functions bound type variables with *some* trait which is implemented for `()`, so falling back in all of those cases removes some of the benefits and can lead to confusing behavior). ```rust= trait Foo { } impl Foo for () { } impl Foo for i32 { } fn gimme<F: Foo>(f: F) { } fn main() { gimme(panic!()); } ``` ## Edition-boundary stabilization? One of the common thoughts to stabilizing `!` is a question of whether we can make inference behave differently depending on the edition of the code. Largely, it seems like the answer here is no: * If `std::convert::Infallible = !`, then inference fallback changes must be enabled (so Infallible would be different types on different editions, seems bad/impractical). * Not clear how to cope with macros -- `?T` is not obviously tied to a particular token in many cases. ## uninhabited match `match x: ! {}` or `match x: Result<T, !> { Ok(a) => a }` being permitted -- that is, making patterns respect inhabitedness -- is a separate feature, tracked in [#51085](https://github.com/rust-lang/rust/issues/51085). It currently has no real tie to `!` (any uninhabited type works equivalently). # Pre-Meeting Conclusions * v1/v2 Inference algorithm is too complex for viable stabilization * Complexity likely necessary for sound changes to inference * Users are at least partially exposed to complexity (some code just doesn't work as expected for hard to explain reasons) * Ability to scale back complexity (e.g., by removing fallback from some or all code) is likely to be hard/impossible, even across edition boundaries (for the same reasons this is hard) * Motivation for `!` having special coercion behavior seems weak * Clearest rationale seems to be "obviously in unreachable code this is fine" * Avoiding this coercion behavior would permit stabilizing `Infallible = !` (without inference fallback changes), except that we can't avoid it due to `!` already being exposed on stable (including in arbitrary user code via `fn() -> !`) * In general, motivation for `!` feels relatively weak compared to complexity of moving forward with the changes * [Draft RFC](https://rust-lang.github.io/never-type-initiative/RFC.html) attempts to propose a couple motivators, is drawn from original discussion * Primary motivators around e.g. `Result<!, T>` having same size as `T` don't require `!`, `Infallible` works fine for that. Possible paths forward: * Remove support for `!` (aside from stable support) from the compiler (essentially, terminate never type initiative) * Edition-dependent migration away from special coercion behavior for `!`, letting us avoid inference algorithm changes while changing `Infallible = !` aliasing. * Leave never type in limbo -- current implementation *could* be stabilized as-is (largely regression free), modulo complexity and lack of known strong motivation. May also introduce soundness holes due to inference changes. Key question for the meeting: * What are the key goals for `!`? (i.e., motivation section, in some sense) ## Meeting Attendance: Team: Josh, Felix, Scott Others: Mark, Michael Goulet (compiler-errors), Gary, Jane, David Barsky ## Questions / Discussion # Question 1 Josh: "unconstrained variables become !" or "unconstrained variables *can* become !"? # Question 2 Josh: Trying to understand the problem better: shouldn't the `match` example fail because `!` doesn't implement `Default`? Because it shouldn't be possible to "materialize" a `!` by any means other than a diverging computation. * Mark: The match example is a simplified version; it illustrates breaking change w/o fancy fallback. You get UB when Default::default is replaced with `unconstrained_return()`, like in Servo code where the function returned an arbitrary T (transmuting function pointers to materialize a fn() -> T). * josh: It *seems* like that case should become an error as well (because it tries to materialize a `!`), but for a different reason than the match (which should fail because `!` doesn't impl `Default`). * Mark: It's not really possible for us to *know* that you're materializing a `!` -- you might genuinely expect that the function never returns, after all. * Gary: It'll be a post-mono error in that case. * Josh: I'd love to understand why we're so allergic to those. "so?". But that may be too much of a tangent. * Gary: I think it's just that we currently don't have many of those. I personally prefer a post-mono error to runtime panic like we have for `assert_inhabited`. * Josh: :+1: ```rust let x: /*?X*/ = match something() { pattern1 => unconstrained(), // has type `?T` pattern2 => panic!("..."), // has type `!` }; // the match has type `?M` ``` # Question 3 Scott: I think it's critical that we have *a* canonical uninhabited type. The ecosystem wants to be able to implement their traits for it, the same way [we implement a whole bunch of traits on it](https://doc.rust-lang.org/std/primitive.never.html#trait-implementations) (and [on `Infallible` too](https://doc.rust-lang.org/std/convert/enum.Infallible.html#trait-implementations)). Michael: Does that need to be a language-level type though? Can we not just move those implementations to a standard uninhabited enum? (yeah, ^ I guess we impl those on Infallible too.) Gary: We already have `!` as a return type so it is kinda already a language-level uninhabited type. It'll be unfortunate if we have a language-level one *and* a library-level one. # Question 4 Josh: For the case involving closure return type inference (`|| loop {}`), is it that it's *harder* to do the inference in that case, or have we tried it and found it not feasible? Could we propagate the `!` to the closure return type, and handle that case the way people looking at it roughly expect it to work? Scott: Hmm, from an inference perspective, isn't that the same case as `let x = panic!();`, though? Michael: The specific fallback issues with `|| loop {}` have to do with the fact it's a closure, right? It's because we typecheck those bodies separately? Gary: Closures are typechecked together with the containing item. # Question 5 Scott: I'm sad that `Err(x)` being `Err::<!, _>(x)` is turning out to be impractical. Michael: Same with None _not_ being `Option<!>` Josh: :+1: Why *doesn't* this work? I'm not seeing that discussed in the doc. This seems like a primary motivation. Gary: This is the consequence of `!` not really being a bottom type, isn't it? Michael: Yeah, I think the fallback to `Option<()>` messes with that. I recall some discussions about relaxing the `PartialEq` on `Option<T>` for `Option<U>` where `T: PartialEq<U>` having inference issues and one of the solutions being making `!`/`None` smarter about this. But that's a tangent. # Question 6 Josh: Is there research in this area, or *anything* we can draw on, or any expertise we could consult to come up with an inference algorithm here? # Observation 7 > There is no intrinsic benefit to doing so Scott: There is, in an ecosystem evolution sense. Because if people are implementing it for `Infallible` today making everything add the new impls -- with the MSRV complications -- is a bit of a mess. # ~~Example 8~~ Scott: This doesn't work today, right? ```rust= fn foo() -> !; fn bar() -> !{ let x = foo(); x // type error because `x` fell back to `()`, right? } ``` Never mind, it does work: <https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=82518dbebfcd3e6aa3fdf076c6676e7a> # Example 9 ```rust= fn foo() -> Result<!, E>; fn unconstrained_return<T>() -> T; match asdf { A => foo()?, // <-- won't work without coercion or something B => unconstrained_return(), } ``` ```rust= fn foo() -> Result<!, E>; fn unconstrained_return<T>() -> T; match asdf { A => foo()?, B => unreachable!(), } ``` This compiles today: ```rust= fn foo() -> Result<Infallible, E>; match asdf { A => foo()?, B => unreachable!(), } ``` This compiles today: ```rust= match asdf { A => unreachable!(), B => 3, } ``` This doesn't compile today: ```rust= fn foo() -> Infallible; match asdf { A => foo(), B => 3, } ``` And if you want to "raise" `Infallible` to `!`, you make an empty match, like ```rust= match infallible {} ``` so this compiles: ```rust= fn foo() -> Infallible; match asdf { A => match foo() {}, B => 3, } ``` Motivations: * Canonical uninhabited type (that Infallible is an alias for) *

    Import from clipboard

    Paste your markdown or webpage here...

    Advanced permission required

    Your current role can only read. Ask the system administrator to acquire write and comment permission.

    This team is disabled

    Sorry, this team is disabled. You can't edit this note.

    This note is locked

    Sorry, only owner can edit this note.

    Reach the limit

    Sorry, you've reached the max length this note can be.
    Please reduce the content or divide it to more notes, thank you!

    Import from Gist

    Import from Snippet

    or

    Export to Snippet

    Are you sure?

    Do you really want to delete this note?
    All users will lose their connection.

    Create a note from template

    Create a note from template

    Oops...
    This template has been removed or transferred.
    Upgrade
    All
    • All
    • Team
    No template.

    Create a template

    Upgrade

    Delete template

    Do you really want to delete this template?
    Turn this template into a regular note and keep its content, versions, and comments.

    This page need refresh

    You have an incompatible client version.
    Refresh to update.
    New version available!
    See releases notes here
    Refresh to enjoy new features.
    Your user state has changed.
    Refresh to load new user state.

    Sign in

    Forgot password

    or

    By clicking below, you agree to our terms of service.

    Sign in via Facebook Sign in via Twitter Sign in via GitHub Sign in via Dropbox Sign in with Wallet
    Wallet ( )
    Connect another wallet

    New to HackMD? Sign up

    Help

    • English
    • 中文
    • Français
    • Deutsch
    • 日本語
    • Español
    • Català
    • Ελληνικά
    • Português
    • italiano
    • Türkçe
    • Русский
    • Nederlands
    • hrvatski jezik
    • język polski
    • Українська
    • हिन्दी
    • svenska
    • Esperanto
    • dansk

    Documents

    Help & Tutorial

    How to use Book mode

    Slide Example

    API Docs

    Edit in VSCode

    Install browser extension

    Contacts

    Feedback

    Discord

    Send us email

    Resources

    Releases

    Pricing

    Blog

    Policy

    Terms

    Privacy

    Cheatsheet

    Syntax Example Reference
    # Header Header 基本排版
    - Unordered List
    • Unordered List
    1. Ordered List
    1. Ordered List
    - [ ] Todo List
    • Todo List
    > Blockquote
    Blockquote
    **Bold font** Bold font
    *Italics font* Italics font
    ~~Strikethrough~~ Strikethrough
    19^th^ 19th
    H~2~O H2O
    ++Inserted text++ Inserted text
    ==Marked text== Marked text
    [link text](https:// "title") Link
    ![image alt](https:// "title") Image
    `Code` Code 在筆記中貼入程式碼
    ```javascript
    var i = 0;
    ```
    var i = 0;
    :smile: :smile: Emoji list
    {%youtube youtube_id %} Externals
    $L^aT_eX$ LaTeX
    :::info
    This is a alert area.
    :::

    This is a alert area.

    Versions and GitHub Sync
    Get Full History Access

    • Edit version name
    • Delete

    revision author avatar     named on  

    More Less

    Note content is identical to the latest version.
    Compare
      Choose a version
      No search result
      Version not found
    Sign in to link this note to GitHub
    Learn more
    This note is not linked with GitHub
     

    Feedback

    Submission failed, please try again

    Thanks for your support.

    On a scale of 0-10, how likely is it that you would recommend HackMD to your friends, family or business associates?

    Please give us some advice and help us improve HackMD.

     

    Thanks for your feedback

    Remove version name

    Do you want to remove this version name and description?

    Transfer ownership

    Transfer to
      Warning: is a public team. If you transfer note to this team, everyone on the web can find and read this note.

        Link with GitHub

        Please authorize HackMD on GitHub
        • Please sign in to GitHub and install the HackMD app on your GitHub repo.
        • HackMD links with GitHub through a GitHub App. You can choose which repo to install our App.
        Learn more  Sign in to GitHub

        Push the note to GitHub Push to GitHub Pull a file from GitHub

          Authorize again
         

        Choose which file to push to

        Select repo
        Refresh Authorize more repos
        Select branch
        Select file
        Select branch
        Choose version(s) to push
        • Save a new version and push
        • Choose from existing versions
        Include title and tags
        Available push count

        Pull from GitHub

         
        File from GitHub
        File from HackMD

        GitHub Link Settings

        File linked

        Linked by
        File path
        Last synced branch
        Available push count

        Danger Zone

        Unlink
        You will no longer receive notification when GitHub file changes after unlink.

        Syncing

        Push failed

        Push successfully