`!` does not coerce on pattern matching

I have a if cfg! ladder, where I want to set some variable to a &str depending on the target operating system. Since I have not yet tested anything on MacOS I opted to evaluate one member of a tuple struct to unimplemented!(), which has the type ! which I thought always coerces to any other type. This, however does not compile with the error in the title. Here is the full code:

let (prefix_to_remove, file_ext_change) = if cfg!(target_os = "windows") {
        ("", (".dll", ".mexw64"))
    } else if cfg!(target_os = "linux") {
        ("lib", (".so", ".mexa64"))
    } else if cfg!(target_os = "macos") {
        (unimplemented!("MacOS dynamic link library file prefix") , (".dylib", ".mexmeci64"))
    } else {
        panic!("Unknown target os")
    };

The whole error message:

error[E0308]: `if` and `else` have incompatible types
  --> xtask/src/test.rs:63:12
   |
61 |        } else if cfg!(target_os = "linux") {
   |  _____________-
62 | |          ("lib", (".so", ".mexa64"))
   | |          --------------------------- expected because of this
63 | |      } else if cfg!(target_os = "macos") {
   | | ____________^
64 | ||         (unimplemented!("MacOS dynamic link library file prefix") , (".dylib", ".mexmeci64"))
65 | ||     } else {
66 | ||         panic!("Unknown target os")
67 | ||     };
   | ||     ^
   | ||_____|
   |  |_____`if` and `else` have incompatible types
   |        expected `&str`, found `!`
   |
   = note: expected tuple `(&str, (&str, &str))`
              found tuple `(!, (&str, &str))`

I can circumvent the issue with unimplemented!() as _, but this is warned against since the cast is unreachable. It is also possible to replace the whole tuple struct expression with an unimplemented!(...), which then works fine and apparently coerces as expected.

Is the failure to coerce when the never-type is nested inside another type expected and/or wanted behaviour or could it be made to work? At least for me the current behaviour is surprising.

Maybe this ties together with the never type initiative, I am not sure.

3 Likes

Unfortunately, coercions and type inference just fundamentally don't like each other -- the unification algorithms for inference want types to be equal, but anywhere there's a coercion they're not equal. So as I understand it, while it's type-inferring it basically doesn't do any coercions, as you might have seen when passing something to a function taking a generic parameter.

I don't know the rules well enough to say for sure what's happening here, but maybe tuples aren't a coercion point?

Tuples propagate coercion, but let without an annotation isn't a coercion site. Try:

let (prefix_to_remove, file_ext_change): (&str, _) = ...

(You might need more annotations depending on the details.)

2 Likes

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.