"cannot infer type" when using question mark

I'm still new to Rust. I am trying to make a function that does this:

  • Try to read a file in the current working directory's parent directory
  • If that fails, try to read a file in the directory two more levels up
  • Otherwise return an empty string
  • If any error happens (usually the path doesn't exist or the file isn't found), return an empty string as well

I have a solution with .unwrap(), but now I would like to use the question mark operator for learning purposes. I'm using nightly, so try_blocks would be ok if that helps.

Here's my result so far:

use std::env::current_dir;
use std::fs::read_to_string;

fn main() {
    println!("Token: {}", read_token());
}

fn read_token() -> String {
    if let Ok(token) = (|| {
        let cwd = current_dir()?;
        let dir = cwd.parent()?.to_owned();
        if let Ok(token) = read_to_string(&dir.join("token")) {
            return Ok(token);
        }
        if let Ok(token) = read_to_string(&dir.parent()?.parent()?.join("token")) {
            return Ok(token);
        }
        return Ok(String::from(""));
    })() {
        return token;
    };
    return String::from("");
}

I'm currently getting an error that I don't quite understand:

error[E0282]: type annotations needed
  --> src\main.rs:10:19
   |
10 |         let cwd = current_dir()?;
   |                   ^^^^^^^^^^^^^^ cannot infer type

current_dir returns a Result<PathBuf, std::io::Error>.

The ? operator uses the From trait to convert that std::io::Error into whatever error type your closure returns. The compiler can't tell what type you want to convert to. You can tell it by giving the closure an explicit return type:

if let Ok(token) = (|| -> std::io::Result<_> {
    // ...

Oh, I just see that .parent() returns an Option, not a Result. So the whole question mark thing isn't gonna work anyway. :frowning:

As @mbrubeck said, with the addition that you have to pick a return type that all of the ? operands can be turned into - you're using both io::Result<String> and Option<String> types, so the easiest way would be to return an Option<String>, use .ok() on the current_dir call, and turn the Oks into Somes:

if let Some(token) = (|| {
    let cwd = current_dir().ok()?;
    let dir = cwd.parent()?.to_owned();
    if let Ok(token) = read_to_string(&dir.join("token")) {
        return Some(token);
    }
    if let Ok(token) = read_to_string(&dir.parent()?.parent()?.join("token")) {
        return Some(token);
    }
    Some(String::from(""))
})() {
    return token;
}

As a bonus, you don't have to write the return annotation, since Some(x) can be inferred.

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.