rust-lang / rust

Empowering everyone to build reliable and efficient software.
https://www.rust-lang.org
Other
97.8k stars 12.66k forks source link

The Try trait causes str::parse to infer the unit type () #94902

Open spikespaz opened 2 years ago

spikespaz commented 2 years ago

Given the following code:

Playground

fn main() {
    || -> anyhow::Result<_> {
        Ok("5".parse::<_>()? / 5.0_f64)
    }().unwrap();
}

The current output is:

Compiling playground v0.0.1 (/playground)
error[[E0277]](https://doc.rust-lang.org/stable/error-index.html#E0277): cannot divide `()` by `f64`
 [--> src/main.rs:3:30
](https://play.rust-lang.org/#)  |
3 |         Ok("5".parse::<_>()? / 5.0_f64)
  |                              ^ no implementation for `() / f64`
  |
  = help: the trait `Div<f64>` is not implemented for `()`

error[[E0277]](https://doc.rust-lang.org/stable/error-index.html#E0277): the trait bound `(): FromStr` is not satisfied
    [--> src/main.rs:3:16
](https://play.rust-lang.org/#)     |
3    |         Ok("5".parse::<_>()? / 5.0_f64)
     |                ^^^^^ the trait `FromStr` is not implemented for `()`
     |
note: required by a bound in `core::str::<impl str>::parse`

error[[E0283]](https://doc.rust-lang.org/stable/error-index.html#E0283): type annotations needed
 [--> src/main.rs:3:28
](https://play.rust-lang.org/#)  |
3 |         Ok("5".parse::<_>()? / 5.0_f64)
  |                            ^ cannot infer type for type parameter `E`
  |
  = note: multiple `impl`s satisfying `anyhow::Error: From<_>` found in the following crates: `anyhow`, `core`:
          - impl<E> From<E> for anyhow::Error
            where E: 'static, E: std::error::Error, E: Send, E: Sync;
          - impl<T> From<T> for T;
  = note: required because of the requirements on the impl of `FromResidual<Result<Infallible, _>>` for `Result<_, anyhow::Error>`

Some errors have detailed explanations: E0277, E0283.
For more information about an error, try `rustc --explain E0277`.
error: could not compile `playground` due to 3 previous errors

Ideally the output should look like:

   Compiling playground v0.0.1 (/playground)
    Finished dev [unoptimized + debuginfo] target(s) in 1.16s
     Running `target/debug/playground`

If a type is provided to str::parse via the turbofish, rather than leaving it anonymous as _, the code compiles well.

fn main() {
    || -> anyhow::Result<_> {
        Ok("5".parse::<f64>()? / 5.0_f64)
    }().unwrap();
}
ahmadjubair33 commented 2 years ago

Hi, @spikespaz I want to contribute to this repo. Can you please me in which file this code is?

spikespaz commented 2 years ago

@ahmadjubair33 This is a MCVE, the original problematic code can be found here: https://github.com/spikespaz/sorbet-rs/blob/0124efd4a3e1695d47db9c8ccf1c1b6112f4073e/sorbet-color/src/css.rs#L91

glennpratt commented 2 years ago

This is the only conversation I could find on this topic so far, but that's surprising to me:

https://internals.rust-lang.org/t/improved-type-inference-for-operators/5393/4

I guess it might be a challenge if there are multiple implementations of the required trait (like Div<f64> in this case).

glennpratt commented 2 years ago

Also, I don't think Rust would infer this type anyway, but it gives a clearer error message without the try.

fn main() {
    let _: f64 = "5".parse::<_>().unwrap() / 5.0_f64;
}
   Compiling playground v0.0.1 (/playground)
error[E0282]: type annotations needed
 --> src/main.rs:2:30
  |
2 |     let _: f64 = "5".parse::<_>().unwrap() / 5.0_f64;
  |                              ^ cannot infer type

For more information about this error, try `rustc --explain E0282`.

https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=a0554dc4cbac7ac83fd56ecc8cdfae97

Xiretza commented 2 years ago

Here's a standalone reproducer:

use std::any::type_name;

fn generic<T>() -> Option<T>
{
    println!("{}", type_name::<T>());
    panic!()
}

fn concrete() -> Option<usize> {
    generic()?;

    Some(0)
}

fn main() {
    concrete().unwrap();
}

Here, T is inferred to be () for no obvious reason. I expected this to produce a compile error along the lines of "type annotations needed".

I can also reproduce this all the way back to Rust 1.13, so this is not due to try_trait_v2 as I had initially assumed.

Rust 1.13 reproducer ```rust fn generic() -> Result { panic!() } fn concrete() -> Result { generic()?; Ok(0) } fn main() { concrete().unwrap(); } ```
MOZGIII commented 2 weeks ago

no obvious reason

fn main() is really fn main() -> (), so the call to unwrap must (unambiguously!) return (), meaning that T must be ()

Do it differently

fn main() {
    let _ = concrete().unwrap();
}

and you'll get an ambiguity and the expected error.


UPD: I realize I was incorrect, because the ; after unwrap already disconnects the return type of the main from the result of unwrap