{
  "$type": "site.standard.document",
  "bskyPostRef": {
    "cid": "bafyreic2tptbeyamniuee4r23kzcsrokbwjvyoe42hgel6eqo23ypnwnlq",
    "uri": "at://did:plc:ivbknywyskln22er3nkssdhl/app.bsky.feed.post/3mlynilgf3eu2"
  },
  "path": "/t/pre-rfc-improved-ergonomics-for/24336#post_1",
  "publishedAt": "2026-05-16T15:05:13.000Z",
  "site": "https://internals.rust-lang.org",
  "tags": [
    "playground",
    "64715",
    "Ergonomics Initiative",
    "auto-never",
    "Zulip thread where I first raised this",
    "Main never RFC #1216",
    "Main never Tracking issue #35121",
    "Main never stabilisation PR #155499",
    "Tracking issue for reserved impl impl<T> From<!> for T #64715",
    "Never Type initiative book",
    "Auto-Never forum post",
    "Auto-Never blog post",
    "Design meeting 2024-03-13: The never type",
    "scottmcm similar Q on zulip in 2020",
    "reddit: Why does the never type not implement all traits?",
    "internals: Never types and inference (2018/2019)",
    "Blog: What Can Coerce, and Where, in Rust"
  ],
  "textContent": "I would love to gather your feedback to the following idea. When I posted this on zulip the comments I received were:\n\n  1. \"`Foo<!>` might be inhabited\" - I think this proposal works with that consideration\n  2. \"show more about where such things are showing up for you and how you ended up with those types in the first place\" - the below pre-RFC ended up a chunk longer than I was expecting but hopefully addresses this\n\n\n\nMy ask to you:\n\n  1. Does this make sense as a proposal, how would you feel about it?\n  2. Why won't this work? Save me a lot of effort by shooting it down now.\n  3. Would you be willing to help out as a mentor or just give any advice to someone who's writing their first RFC (I post-documented scottmcm's work on try bikeshed, so technically I'm the \"author\" of the pending RFC for that but it's all their work so I'm not counting it)\n\n\n\n# Pre-RFC: improved ergonomics for `!`\n\n## Summary\n\nAllow `!` to be used in mainstream code to signify an impossible value without introducing \"more work than it's worth\". Up to now most of my mainstream usage of `!` has brought reduced ergonomics as the cost of accurate typing.\n\nI propose to provide a limited form of coercion for the most common & painful usages of `!`, in a way which moves the discussion away from whether `Foo<!>` is inhabited. I imagine that the implementation would occur reasonably early in the compilation alongside type-inferance and bounds validation. I would be more than happy to put in the work to research, identify, discuss, implement and shepherd such a change (but would be very grateful if I could find a willing mentor).\n\n## Motivation\n\nThe stabilisation of never is (hopefully) just around the corner (a huuuuge thank you to _everyone_ who has been part of getting it this far). Please please, please do not take this as a criticism - rather a compliment as to how valuable your efforts are to people like me who love to code in rust (you may get a feeeling for how excited I am to be able to make even more use of `!`).\n\nWe should expect increased use of `!` in the future to explicitly highlight situations which _cannot_ occur. Currently, using `!` to accurately and explicitly anchor this information in the type system and lead to unfortunate foot guns.\n\nIn the past 2 months I have run into the following situations where `!` is the _right_ answer, but not the _pragmatic_ answer.\n\n### Examples\n\n#### Async: reset io readiness & Poll::Pending\n\nBefore using an io connection it is often necessary to check readiness. These checks can leave the connection in an undesired state and need to be reset if not used.\n\nA related clear function can (semantically) only return `Poll::Pending` or `Poll::Ready(Err)`. Any form of `Poll::Ready(Ok)` is meaningless. As such the *correct_ signature would be `fn clear_ready(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<io::Result<!>>;`, which fully conveys these semantics without users needing to read the full set of notes in the documentation.\n\nThis signature, however, causes issues down the road, for example when implementing `Stream`\n\nplayground\n\n\n    /// Async polling for a socket\n    trait PollableSocket\n    where\n        Self: Sized,\n    {\n        /// Clear the readiness state of the underlying socket.\n        ///\n        /// **This MUST be called after any failed readiness poll.**\n        ///\n        /// Implementations should attempt to clear the relevant readiness marker of the underlying\n        /// socket and then return:\n        /// - `Poll::Pending` if successful\n        /// - `Poll::Ready(error)` on error, to avoid repeated polling without handling the error\n        fn clear_ready(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<io::Result<!>>;\n\n        /// Check whether the socket is ready.\n        ///\n        /// ## Note\n        ///\n        /// You **MUST** call self.clear_ready() in the following cases:\n        ///\n        /// - If this fails it may leave the socket in an undefined readiness state.\n        /// - If you do not make use of the readiness it will remain blocked in that state.\n        fn poll_ready(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<io::Result<Ready>>;\n    }\n\n    impl Stream for MySocket {\n        type Item = io::Result<String>;\n\n        fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {\n            match ready!(self.as_mut().poll_ready(cx)) {\n                Ok(readiness) if readiness.contains(Ready::READ) => todo!(\"read and stream\"),\n                _ => self.clear_ready(cx).map_ok(|x| x).map(Some), // <- .map_ok(|x| x) to coerce ! to String\n            }\n        }\n    }\n\n\nNote that the call to clear ready needs to be followed by a no-op `.map_ok(|x| x)` in `_ => self.clear_ready(cx).map_ok(|x| x).map(Some)`.\n\nIn this case we are lucky that `Poll` offers a convenience function `.map_ok()` to manipulate the wrapped result. Most types do not.\n\nWithout this convenience (or the convenience of `ready!`) the code expands to a verbose match:\n\n\n    _ => match self.clear_ready(cx) {\n        Poll::Pending => Poll::Pending,\n        Poll::Ready(Err(e)) => Poll::Ready(Some(Err(e))),\n    }\n\n\nThis may seem trivial when reading later. The surrounding code is, by it's very nature, inherently complex; the requirement to add a no-op map adds a completely different dimension of complexity and thus risk, requiring the user to context-switch (I certainly found this cognitively taxing and something that completely threw my focus from the actual implementation).\n\n#### Infallible conversions & trait bounds\n\nThe second case is probably going to be more common in the wild. While implementing a parsing library I:\n\n  * Defined a custom error type\n  * Created a series of custom types to represent the parsed data\n  * Implemented `FromStr` for those custom types\n  * Added a basic marker-ish trait `Header`with any type-specific implementation details (e.g. the header key)\n  * Added `HeaderExt` with a blanket impl to parse the value from a header structure\n\n\n\nSo far ... nothing magical or unusual. The issue arises around how to handle cases where `FromStr` is infallible.\n\nThe *right_ way to do this would be:\n\n\n    impl FromStr for DeviceType {\n        type Err = !;\n        ...\n\n\nThen it is clearly defined in the type system that this conversion can never fail, which again fully conveys the semantics without users needing to read the full set of notes in the documentation.\n\nHowever, this means that the blanket\n\n\n    impl<H, E> HeaderExt for H\n    where\n        H: Header + FromStr<Err = E>,\n        HeaderErr: From<E>,\n\n\nwill not trigger.\n\nHere is a full skeleton example playground\n\n\n    #![allow(dead_code)]\n    #![allow(unused_variables)]\n    #![feature(never_type)]\n\n    use std::str::FromStr;\n\n    enum HeaderErr {\n        ParseError,\n    }\n\n    enum DeviceType {\n        AudioController,\n        Custom(String),\n    }\n\n    impl FromStr for DeviceType {\n        // We have a `Custom` type so this will never fail\n        type Err = !;\n\n        fn from_str(s: &str) -> Result<Self, Self::Err> {\n            let devicetype = match s {\n                \"AudioController\" => Self::AudioController,\n                _ => Self::Custom(s.to_string()),\n            };\n            Ok(devicetype)\n        }\n    }\n\n    trait Header {}\n\n    impl Header for DeviceType {}\n\n    trait HeaderExt\n    where\n        Self: Sized,\n    {\n        /// Parse data from a header line ()\n        fn parse_header(header: &str) -> Result<Self, HeaderErr>;\n    }\n\n    impl<H, E> HeaderExt for H\n    where\n        H: Header + FromStr<Err = E>,\n        HeaderErr: From<E>,\n    {\n        /// Parse data from a header line ()\n        fn parse_header(header: &str) -> Result<H, HeaderErr> {\n            let (data, checksum) = header.split_once(\", sha:\").ok_or(HeaderErr::ParseError)?;\n            Ok(data.parse()?)\n        }\n    }\n\n    fn main() {\n        // let device =\n        //     DeviceType::parse_header(\"AudioController, sha:040f4bf53d2ca137d6f767169cdb2fa62849b156\");\n    }\n\n    // error[E0599]: the variant or associated item `parse_header` exists for enum `DeviceType`, but its trait bounds were not satisfied\n    //   --> examples/conversion.rs:57:21\n    //    |\n    // 11 | enum DeviceType {\n    //    | --------------- variant or associated item `parse_header` not found for this enum\n    // ...\n    // 57 |         DeviceType::parse_header(\"AudioController, sha:040f4bf53d2ca137d6f767169cdb2fa62849b156\");\n    //    |                     ^^^^^^^^^^^^ variant or associated item cannot be called on `DeviceType` due to unsatisfied trait bounds\n    //    |\n    // note: the following trait bounds were not satisfied:\n    //       `&DeviceType: Header`\n    //       `&DeviceType: std::str::FromStr`\n    //       `&mut DeviceType: Header`\n    //       `&mut DeviceType: std::str::FromStr`\n    //       `<&DeviceType as std::str::FromStr>::Err = _`\n    //       `<&mut DeviceType as std::str::FromStr>::Err = _`\n    //   --> examples/conversion.rs:45:8\n    //    |\n    // 43 | impl<H, E> HeaderExt for H\n    //    |            ---------     -\n    // 44 | where\n    // 45 |     H: Header + FromStr<Err = E>,\n    //    |        ^^^^^^   ^^^^^^^^^^^^^^^^\n    //    |        |        |       |\n    //    |        |        |       unsatisfied trait bound introduced here\n    //    |        |        unsatisfied trait bound introduced here\n    //    |        unsatisfied trait bound introduced here\n    //    = help: items from traits can only be used if the trait is implemented and in scope\n    // note: `HeaderExt` defines an item `parse_header`, perhaps you need to implement it\n    //   --> examples/conversion.rs:35:1\n    //    |\n    // 35 | trait HeaderExt\n    //    | ^^^^^^^^^^^^^^^\n\n\n\nThere are two ways around this:\n\n  1. (The lazy one) just define\n\n         /// We have a `Custom` type so this will never *actually* fail\n         impl FromStr for DeviceType {\n             type Err = HeaderErr;\n         ...\n\n\n  2. (The _right_ way, which currently compiles but adds another case of future collision with the planned blanket impl in #64715) add\n\n         impl From<!> for HeaderErr {\n             fn from(value: !) -> Self {\n                 match value {}\n             }\n         }\n\n\n\n\n\n#### Option wrapping\n\nIt doesn't take a large amount of imagination to envision `Option<Result<!,E>>` or `Option<Result<T,!>>` resulting from similar starting situations to the above examples. Would the recommendation for `Option<Result<!,E>>` be:\n\n  * nested maps: `.map(|r| r.map(|never| never))`\n  * double transposition: `.transpose().map(|_never| None).transpose()`\n  * map try: `.map(|e| try {e?})`\n  * Don't use `Result<!,E>` to represent 'only returns on error' but stick with `Result<(),E>` which was used before we had `!`\n\n\n\nAnd for those wondering where this would come from, I originally split out a common error handler in the async example above, but then just inlined it instead: playground\n\n\n    #![feature(never_type)]\n    #![feature(try_blocks)]\n    use std::io;\n\n    fn ignore_blocking(err: io::Error) -> Option<io::Result<!>> {\n        match err.kind() {\n            // This could just as easily be any error we want to ignore and move on\n            // (e.g. `PermissionDenied | ReadOnlyFileSystem | IsADirectory`) when updating\n            // \"all available files\". Possibly with a call to `info!()` to log.\n            io::ErrorKind::WouldBlock => None,\n            _ => Some(Err(err)),\n        }\n    }\n\n    pub fn process(input: u32) -> Option<io::Result<u32>> {\n        let io_function = Ok(input);\n        match io_function {\n            Ok(_) => Some(io_function),\n            Err(e) => ignore_blocking(e) // hopefully in future we can just add a `,` here\n            .map(|e| try {e?}), // currently we need to convert Option<Result<!>> to Option<Result<u32>>\n        }\n    }\n\n\n(Yes the error handler _could_ just return `Option<io::Error>` and leave it to the caller to wrap in a `Result`, but wouldn't it be nicer to hand back a type structure that the caller can simply use?)\n\n### Why bother? - there are clear workarounds for each case\n\n`!` is great! It extends the language to provide a clear way to idiomatically express intent. From the point of view of a general language user, I'd consider it as valuable as `None` (is not Null) and `Result` (is neither a tuple nor an exception) in this regard. It therefore deserves a focus on integrative ergonomics in the surrounding language, separately from the core implementation.\n\n  1. We should expect `Result`, fallible traits and error-handlers to be the most common cases where people begin to use `!`. If these obvious usages cause \"pain\" shortly down the road then, sadly, most will simply replace `!` with a dummy value/type and move on.\n  2. All the reasonable workarounds rely on some form of `map` function. `Poll` makes this easiest by providing a map to the inside `T` of a double-wrapped `Poll<Option<Result<T,E>>>`; `Option` doesn't offer this (for good reasons) but at least has its own `map` which allows chaining. As `Try` nears stabilisation and then gets into stable we should expect an increased number of custom wrapper types; many of which may not think to offer a `map`. This leaves the user stuck with verbose match destructuring; or avoiding either `!` or the custom try type (or both).\n\n\n\n### Ergonomics\n\nThe 2017 [Ergonomics Initiative] lays out 3 dimensions to balance when looking at providing implicitness for reasons of ergonomics.\n\n#### Applicability (4/5)\n\n  * Strictly excluding `match` etc. from consideration removes the side-effects that made previous considerations impossible at the cost of slightly reduced applicability.\n  * The coercion is restricted to only cover situations with `<!>` as a generic type, generic type bound or an associated type.\n\n\n\n#### Power (2/5)\n\n  * Converting _from_ `Foo<!>` to `Foo<T>` will never destroy any information. Or rather, the implicit conversion will only take effect _if it is safe to do so_.\n  * By performing this as part of the type-safety & generics analysis no runtime conversion of data occurs.\n  * No memory access or implicit dereferencing occurs.\n\n\n\n#### Context-Dependence (2/5)\n\n  * By restricting to situations where type-inference is already expected the overall influence is restricted to _at most_ the current function / trait impl boundary as return types are always explicit. The user only needs to look at two function / trait signatures which are immediately adjacent to the current code to see `!` incoming and `T` outgoing.\n  * Additionally rust-analyzer is commonly used and provides inline details of the explicit & inferred types directly in place in the code for most users.\n\n\n\n## How could this be implemented?\n\nThe HIR is currently used to perform type-inference, trait solving & type-checking. The viability of coercion requires the same data and can be verified in the HIR at the same time, probably as part of the existing steps. It may be necessary / possible to leverage some form of monomorphisation later in the MIR, or to provide targeted MIR optimisations. Right now I just have a high-level idea of where to start looking to see if I can find a viable implementation.\n\n## This won't work because `Foo<!>`, `&!` etc are not guaranteed to be uninhabited\n\nThat's less relevant given the restrictions on this solution:\n\n  1. No usage in `match` etc. - so no crossover with the concerns around memory access & dereferencing in the context of unsafe code discussed in [auto-never].\n  2. The compiler already has the information in the HIR and uses it for similar validations. For example see the error returned when attempting to implement map below playground:\n\n\n\n\n    #![feature(never_type)]\n    #![allow(dead_code)]\n\n    #[derive(Debug)]\n    struct Foo<T: HasAssocType> {\n        data: T::AssocType,\n    }\n\n    trait HasAssocType: Sized {\n        type AssocType;\n    }\n\n    impl HasAssocType for ! {\n        type AssocType = [u8; 0];\n    }\n\n    impl HasAssocType for u8 {\n        type AssocType = [u8; 1];\n    }\n\n    // // error[E0308]: mismatched types\n    // //   --> examples/generic.rs:43:20\n    // //    |\n    // // 38 |     fn map<U, F>(self, f: F) -> Foo<U>\n    // //    |            - found this type parameter\n    // // ...\n    // // 43 |         Foo{ data: f(self.data) }\n    // //    |                    ^^^^^^^^^^^^ expected associated type, found type parameter `U`\n    // //    |\n    // //    = note: expected associated type `<U as HasAssocType>::AssocType`\n    // //                found type parameter `U`\n    // // help: consider further restricting this bound\n    // //    |\n    // // 40 |         U: HasAssocType<AssocType = U>,\n    // //    |                        +++++++++++++++\n    //\n    // impl<T: HasAssocType> Foo<T> {\n    //     fn map<U, F>(self, f: F) -> Foo<U>\n    //     where\n    //         U: HasAssocType,\n    //         F: FnOnce(T) -> U,\n    //     {\n    //         Foo{ data: f(self.data) }\n    //     }\n    // }\n\n    fn main() {\n        let never_foo = Foo::<!> { data: [] };\n\n        let u8_foo = Foo::<u8> { data: [1] };\n\n        println!(\"{never_foo:?}, {u8_foo:?}\");\n    }\n\n\n## References\n\n  * Zulip thread where I first raised this\n  * Main never RFC #1216\n  * Main never Tracking issue #35121\n  * Main never stabilisation PR #155499\n  * Tracking issue for reserved impl impl<T> From<!> for T #64715\n  * Never Type initiative book\n  * Auto-Never forum post\n  * Auto-Never blog post\n  * Design meeting 2024-03-13: The never type\n  * scottmcm similar Q on zulip in 2020\n  * reddit: Why does the never type not implement all traits?\n  * internals: Never types and inference (2018/2019)\n  * Ergonomics Initiative\n  * Blog: What Can Coerce, and Where, in Rust\n\n",
  "title": "Pre-RFC improved ergonomics for `!`"
}