Rust: Match statement mismatched type error for enum Result

613 views Asked by At

I'm still a Rust noob and I'm writing a CLI tool that reads user input and makes API calls or local DB queries through a match statement. The error I'm encountering is on the line "new" => util::db::insert_number(&conn, user_input).await I'm able to make calls to functions with no return type (banner() & desc()) and return type Result outside of the match statements, but not inside. Could someone put me on the right track to resolve this issue.

TLDR: match statement expects return type (), but I want to call funcs with return type Result<()>

ERROR:

expected unit type `()` found enum `Result<(), rusqlite::Error>

cli.rs/main_loop():

pub async fn main_loop() -> Result<()> {
    banner();
    desc();
    
    let conn = Connection::open("db.db").expect("connection failed");
    util::db::check_db(&conn).await;

    let mut user_input: Vec<String>;
    let mut rl = Editor::<()>::new();
    if rl.load_history(".history").is_err() {
           println!("no previous history...");
    }
    println!("\t\t type 'new <number>' to add a number to the db");
    println!("\t\t     type 'exit' to leave configuration mode\n");
    loop {
        let readline = rl.readline("CONFIG# ");
        match readline {
            Ok(line) => {
                user_input = get_string_vec(line);
                match user_input[0].as_str() {
                    "new" => util::db::insert_number(&conn, user_input).await,
                    "exit" => break,
                    _ => continue,
                }
            },
            Err(ReadlineError::Interrupted) => {
                println!("ctrl+c pressed. quitting now..");
                std::process::exit(0);
            },
            Err(ReadlineError::Eof) => {
                println!("ctrl+d pressed. quitting now..");
                std::process::exit(0);
            },
            Err(err) => {
                println!("error: {:?}", err);
                std::process::exit(0);
            }
        }
    }
    Ok(())
}

db.rs/insert_number():

pub async fn insert_number(conn: &Connection, args: Vec<String>) -> Result<()> {
    //let conn = Connection::open("db.db").expect("connection failed");
    conn.execute(
        "insert into numbers (number) values (?1)",
        &[args[1].as_str()],
    ).expect("insert failed");
    Ok(())
}

I've tried removing all cases except the case that returns Result<()>

1

There are 1 answers

1
jthulhu On BEST ANSWER

Let's remove from your code everything that is noise to understand where the error comes from (ignore the fact that some variable names are now undefined):

loop {
    match readline {
        Ok(line) => {
            match user_input[0].as_str() {
                "new" => insert_number(&conn, user_input).await,
                _ => continue,
            }
        },
        _ => {}
    }
}

When Rust tried to typecheck this, among other things it will apply a rule that says that all branches of a match statement/expression should have the same type, which is the type of the match expression overall. In particular, if you apply this rule to the outer match, you get that the inner match must has type (). Then, by applying this rule again, you get that util::db::insert_number(&conn, user_input).await must have type (). Thus the error. This is because the first branch of the outer match returns the value of the inner match.

Thus, a very simple fix is simply

loop {
    match readline {
        Ok(line) => {
            match user_input[0].as_str() {
                "new" => insert_number(&conn, user_input).await,
                _ => continue,
            };
//           ^
        },
        _ => {}
    }
}

We have now enforced that the first branch of the inner match will ditch the result of the inner match, and return () by adding a semicolon. Similarly, you could have wrapped the call to insert_number in drop(...), which means "ignore this value and return ()" like so:

loop {
    match readline {
        Ok(line) => {
            match user_input[0].as_str() {
                "new" => drop(insert_number(&conn, user_input).await),
//                       ^^^^^                                      ^
                _ => continue,
            }
        },
        _ => {}
    }
}

There is one thing that I've left unsaid, however. This code compiles, but a warning is emitted: warning: unused `Result` that must be used. The compiler is telling you that you have ditched a value of type Result, which most of the time is not what you wanted to do.

This is because the type Result in Rust corresponds to error propagation. Ignoring a value of type Result means possibly ignoring an error, which is probably not what you want to do. Instead, Rust assumes you want to handle that error someway.

The easiest way to handle an error is simply to make the program crash if an error happens. This can be done with .unwrap()

loop {
    match readline {
        Ok(line) => {
            match user_input[0].as_str() {
                "new" => insert_number(&conn, user_input).await.unwrap(),
//                                                             ^^^^^^^^^
                _ => continue,
            }
        },
        _ => {}
    }
}

Neat, now it compiles and it doesn't give any warnings. But... did you notice how that function politely gave you the possibility to handle an error, by returning a Result<(), ...> instead of just crashing if it reached an error itself. Most likely, the caller of the function you are writing also expects your function to be this nice as well. Indeed, your function also has a return type Result<(), ...> (note that Result<()> is an alias for Result<(), T> with some predefined T). This would be "error propagation": leave the caller handle the error, or propagate it to someone who actually wants to handle the error. This can be done pretty easily:

loop {
    match readline {
        Ok(line) => {
            match user_input[0].as_str() {
                "new" => match insert_number(&conn, user_input).await {
                    Ok(()) => (),
                    Err(err) => return Err(err),
                },
                _ => continue,
            }
        },
        _ => {}
    }
}

Neat now this (probably) works. I say probably because maybe you need to transform your return Err(err) into a return Err(err.into()), depending on the actual error types. It's simple and elegant, however it's also very verbose: I've added three lines just to propagate an error of a single expression. It would be convenient if there was some kind of short macro that did exactly this for me, right...

loop {
    match readline {
        Ok(line) => {
            match user_input[0].as_str() {
                "new" => insert_number(&conn, user_input).await?,
//                                                             ^
                _ => continue,
            }
        },
        _ => {}
    }
}

Yes, ? does exactly that.