Async with result is confusing me

Based on the following doc: ? in async Blocks - Asynchronous Programming in Rust

I could not understand what do I actually need to do in order to get a result's value.

pub async fn connect() -> Result<DatabaseConnection, DbErr> {
    return Database::connect("sqlite::memory:").await;
}

pub async fn migrate_up(db: &DatabaseConnection) -> Result<(), DbErr> {
    return Migrator::up(db, None).await;
}

The following example:

#[tokio::test]
async fn ok() {
    let db = connect().await?; // the error occurs here
    let result = migrate_up(&db).await;
    assert!(result.is_ok());
}

gives me the following very confusing error:

the ? operator can only be used in an async block that returns Result or Option (or another type that implements FromResidual)
the trait FromResidual<Result<Infallible, sea_orm::DbErr>> is not implemented for ()

Can someone please help with understanding how to get a value of the result without going viral with some crazy syntax?

The problem is in your function signature:

async fn  ok() {

This implies no return. But in order to use ?, your function HAS to return a Result, e.g.

async fn ok() -> Result<(), E> {

where E is something that satisifes the error trait (maybe DbErr? I would have to check if that works)

Edit:
Forgot to add: Then, of course, your function has to return a result in the happy path, i.e.

    assert!(result.is_ok());
    Ok(())
}
1 Like

Thanks @KSwanson , I got the point that the "?" has to return the actual error, not panic.

So there is a problem then. In my situation I would want to execute multiple different async functions which could return different types of errors. Eg:

let v1 = func1().await?;
let v2 = func2().await?;

So I am going to find a solution which does not require a Return<> type for the test ok() function. Is it possible? Can the "Result" panic instead?

Ok, I guess I have got a solution:

let db = connect().await.ok().unwrap();

Thanks @KSwanson again :+1:

1 Like

(for posterity's sake)

So there's more or less 2 ways of dealing with this.

Option 1: Make your own Error Type

The way I usually do this is with an enum and derive_more:

use derive_more::{Debug, Display, Error, From}
enum MyError {
    Db(DbErr),
    // as an example of another type of error you might get
    Io(std::io::Error),
}

async fn my_func() -> Result<(), MyError> {
   // Or whatever your function does
   Ok(())
}

Option 2: Panic

(I see this is the option you took)

In general it is better to return a Result<T,E> than to panic. But sometimes it doesn't really matter and you might as well just panic!. The most common case is when you are in a test, as tests rely on assert! and panic functionality; so why not panic anyway.

The simplest way to do this is to use the expect method:

let v1 = func1.await.expect("func1 failed");

(I see that you used unwrap, which is similar; but expect() takes a message to display in the panic output, which is often nice, and why I prefer it)

There are other options to handle results--you can use ok_or if there is a reasonable default value to use in case of an Err, for example.

2 Likes

If all the errors implement Error you can also return Result<(), Box<dyn Error>>[1]


  1. possibly with some auto traits like Send added if necessary ↩ī¸Ž

4 Likes

Does ok().unwrap() throw a proper message, so I know what is going on when error (panic) occurs?
For example the DbError looks like this:

/// An error from unsuccessful database operations
#[derive(Debug, PartialEq, Eq, Clone)]
pub enum DbErr {
    /// There was a problem with the database connection
    Conn(String),
    /// An operation did not execute successfully
    Exec(String),
    /// An error occurred while performing a query
    Query(String),
    /// The record was not found in the database
    RecordNotFound(String),
    /// A custom error
    Custom(String),
    /// Error occurred while parsing value as target type
    Type(String),
    /// Error occurred while parsing json value as target type
    Json(String),
    /// A migration error
    Migration(String),
}

Update: Looks like it does not.
I changed the db connection string so it is not correct, and I have got the following error:

thread 'tests::ok' panicked at 'called Option::unwrap() on a None value', data\src/lib.rs:37:53

Would be nice to include the actual error message from DbError.

No, but for a minor point I missed in your original post:

connect().await.ok().unwrap()

The ok() method turns it into an Option, which throws away the DbErr. So you get no information on the error.

Fortunately there's an easy solution: unwrap and expect both exist directly on Result, and in both cases they will print out the contents of the Error (assuming the Error implements Debug, which DbErr does in your case).

So you can change it to

connect().await.unwrap()

It can be nice to use expect, though, if you happen to have multiple calls to the same method, e.g.

query().await.expect("query 1 failed");
query().await.expect("query 2 failed");

Edit:
or if you're doing something in a loop, e.g.

for input in input_vec {
    func(input).await.expect(&format!("failed on input {}", input));
}
6 Likes

Thank you so much. I simply used unwrap() and I have got exactly what I wanted :partying_face: :+1:

Now the error is the following (which is what I was looking for):

thread 'tests::ok' panicked at 'called Result::unwrap() on an Err value: Conn("error returned from database: (code: 14) unable to open database file")', data\src/lib.rs:37:48

1 Like

This might be better (yet more verbosely) expressed with unwrap_or_else(|e| panic!("failed on input {}: {}", input, e)), since your code will allocate the error message eagerly, before even checking whether there was an error at all.

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.