{
  "$type": "site.standard.document",
  "bskyPostRef": {
    "cid": "bafyreidksy5uv2zzp6c3jpohkpytkknjw36axrw2q76h65we5x7pys4wme",
    "uri": "at://did:plc:ivbknywyskln22er3nkssdhl/app.bsky.feed.post/3mmhd4t3lxqs2"
  },
  "path": "/t/pre-rfc-improved-ergonomics-for/24336#post_13",
  "publishedAt": "2026-05-22T13:17:00.000Z",
  "site": "https://internals.rust-lang.org",
  "tags": [
    "playground"
  ],
  "textContent": "robofinch:\n\n> As for relying on `Try`… it seems like implementing this mapping/coercion would get very overengineered.\n\nThis conversation has been really valuable, thanks!\n\nI've spent the last couple of days working through why `Option<io::Result<!>>` feels so different to `Vec<io::Result<!>>`. The latter is certainly not something that should be implicitly converted to a `Vec<io::Result<MasssiveStruct>>` ...\n\nFrom that and more experimentation I've realised that this feeling of unergonomic developer experience comes about from the interplay of two `!` aspects with `?` (which already uses `Try` under the hood).\n\nCompare the following (without `!`) playground\n\n\n    fn ignore_blocking_not_never(err: io::Error) -> io::Result<std::convert::Infallible> {\n        Err(err)\n    }\n\n    // Recognition of ! as infallible appears much earlier in process than Infallible\n    pub fn process_return_result_not_never(input: u32) -> io::Result<u32> {\n        let io_function = Ok(input);\n        match io_function {\n            Ok(_) => io_function,\n            Err(e) => {\n                let r = ignore_blocking_not_never(e); // InferredType `r: io::Result<Infallible>`\n                let _b = r?; // InferredType `_b: Infallible`\n                Err(io::Error::other(\"Infallible is not recognised as divergent by HIR, only at MIR\"))\n            }\n        }\n    }\n\n\nwith the `!` equivalent playground\n\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    // To show the confusion & relevance to ! from a slightly different perspective\n    pub fn process_return_result_long(input: u32) -> io::Result<u32> {\n        let io_function = Ok(input);\n        match io_function {\n            Ok(_) => io_function,\n            Err(e) => {\n                let o: Option<Result<!, io::Error>> = ignore_blocking(e);\n                let r = o.unwrap(); // InferredType `r: io::Result<!>`\n                let _b = r?; // InferredType `_b: io::Result<u32>`\n                #[allow(unreachable_code)]\n                _b // With ! this is unreachable\n            }\n        }\n    }\n\n\nWhile `Infallible` is recognised by the MIR as unreachable (as confirmed running \"show MIR\" on the by the first playground) the HIR doesn't see this, so the user is used to adding `unreachable!()` or similar to avoid a compiler error.\n\nWith `!` the code is recognised as unreachable by the HIR which then leads to a linter error. That's a major UX change from \"compiler error if you don't\" to \"linter error if you do\" (add a return)\n\nAlso - the inferred return types of `_b` are significantly different (for the same reason)\n\nNow couple this with the _appearance_ of automatic coercion from un-nested Try-types (seen above in the type of `_b` and more explicitly in the return to the original example below) playground\n\n\n    // Easier to see what is going on if we explicitly use try blocks\n    pub fn process_try_try(input: u32) -> Option<io::Result<u32>> {\n        let io_function = Ok(input);\n        match io_function {\n            Ok(_) => Some(io_function),\n            Err(e) => {\n                try {\n                    let o: Option<io::Result<!>> = ignore_blocking(e);\n\n                    // It _looks like_ this coerces an Option<io::Result<!>>::None,\n                    // to an Option<io::Result<u32>>::None, but see below for what\n                    // is really happening\n                    let r: io::Result<!> = o?;\n\n                    // And this _appears to_ coerce an io::Result<!>::Err to an\n                    // io::Result<u32>::Err, but, again, see below for reality.\n                    try { r? }\n                }\n            }\n        }\n    }\n\n    // Which desugars and simplifies to:\n    pub fn process_desugared(input: u32) -> Option<io::Result<u32>> {\n        type OptionResultNever = Option<io::Result<!>>;\n        type ResultNever = io::Result<!>;\n        type OptionResultU32 = Option<io::Result<u32>>;\n        type ResultU32 = io::Result<u32>;\n\n        let io_function = ResultU32::Ok(input);\n        match io_function {\n            Ok(_) => OptionResultU32::Some(io_function),\n            Err(e) => {\n                let outer_try: OptionResultU32 = 'outer_try: {\n                    let o: OptionResultNever = ignore_blocking(e);\n                    let r: ResultNever = match o {\n                        OptionResultNever::Some(r) => r,\n                        // Automatic, hidden, explicit type conversion in desugared version\n                        OptionResultNever::None => break 'outer_try OptionResultU32::None,\n                    };\n                    let inner_try: ResultU32 = match r {\n                        // Automatic, hidden, explicit type conversion in desugared version\n                        ResultNever::Err(e) => ResultU32::Err(e),\n                    };\n                    // Automatic, hidden, explicit type conversion in desugared version\n                    OptionResultU32::Some(inner_try)\n                };\n\n                outer_try\n            }\n        }\n    }\n\n\nSo, what I'm tending towards at the moment, is that the real ergonomics issue (and point of likely confusion) is with nested `Try` types and `!`. That feels like it's pointing towards a specific solution, maybe fully implicit, maybe requiring a minimal explicit syntax involving `?` ... (Not quite there yet but putting this thought process out there, both to help me structure thoughts and see where it resonates with / triggers others)",
  "title": "Pre-RFC improved ergonomics for `!`"
}