Error propagation "?" encapsulated (try/catch)!

Hello,

the operator "?" for Error propagation is very useful when you have to deal with a lot of errors. the only problem is that it acts as a: return Err(xx);

but sometimes you need to manage a group of errors locally, as with java's try/catch.

for example, if you want to return a very specific type of error : ( I don't want to expose the sqlx error directly )

pub async fn test(dbex: impl PgExecutor<'_>) -> Result<i64, RepositoryError> {
    match sqlx::query(r#"SELECT A, B, C FROM te_test"#)
        .fetch_one(dbex)
        .await
    {
        Ok(ret) => {
            let a =  ret.try_get(0)?; // It won't work, because you have to manage the "match" manually. 
            let b =  ret.try_get(1)?;
            let c =  ret.try_get(2)?;
             Ok(a+b+c)
        }
        Err(_e) => Err(RepositoryError::FatalError {
            detail: "Database error".to_string(),
        }),
    }
}

this code will not work, unless you put "match" for all "try_get", what makes the code ugly.

let val1 = match ret.try_get(0) {
                Ok(c) => c,
                Err(_) => {
                    return Err(RepositoryError::ColumnNotFound {
                        detail: "Column not found".to_string(),
                    });
                } 
   };

let val2 = match ret.try_get(1) {
                Ok(c) => c,
                Err(_) => {
                    return Err(RepositoryError::ColumnNotFound {
                        detail: "Column not found".to_string(),
                    });
                }
            };
let val3 = match ret.try_get(2) {
                Ok(c) => c,
                Err(_) => {
                    return Err(RepositoryError::ColumnNotFound {
                        detail: "Column not found".to_string(),
                    });
                }
            };

so I'm thinking, why limit the "?" to the function result?

it would be nice if we could do something like this :

try {
       let a =  ret.try_get(0)?;
       let b =  ret.try_get(1)?;
       let c =  ret.try_get(2)?;
       Ok(a+b+c)
} catch(e) {
        return Err(RepositoryError::ColumnNotFound {
            detail: "Column not found".to_string(),
        });
}

and so the final code becomes :

pub async fn test(dbex: impl PgExecutor<'_>) -> Result<i64, RepositoryError> {
    match sqlx::query(r#"SELECT A, B, C FROM te_test"#)
        .fetch_one(dbex)
        .await
    {
        Ok(ret) => {
                    try {
                            let a =  ret.try_get(0)?;
                            let b =  ret.try_get(1)?;
                            let c =  ret.try_get(2)?;
                            Ok(a+b+c)
                    } catch(e) {
                            return Err(RepositoryError::ColumnNotFound {
                                         detail: "Column not found".to_string(),
                             });
                    }
        }
        Err(_e) => Err(RepositoryError::FatalError {
            detail: "Database error".to_string(),
        }),
    }
}

with try/catch you can manage a group of errors locally .

https://doc.rust-lang.org/beta/unstable-book/language-features/try-blocks.html

8 Likes

I know this is not what you are asking for, but you can fake the try/catch syntax with closures and Result combinators: Rust Playground

2 Likes

Thank you for this information

Very interesting your tip :+1:

You can use map_err instead.

let err = |_| Err(RepositoryError::ColumnNotFound {
                        detail: "Column not found".to_string(),
                    });
let a =  ret.try_get(0).map_err(err)?;
let b =  ret.try_get(1).map_err(err)?;
let c =  ret.try_get(2).map_err(err)?;
2 Likes

if RepositoryError is in your crate, you can also

impl From<sqlx::Error> for RepositoryError {
  fn from(_: sqlx::Error) -> Self {
    RepositoryError::ColumnNotFound {
                        detail: "Column not found".to_string(),
                    }
  }
}

and it will enable the ? operator for your code

2 Likes

This was going to be my original suggestion, but then I realized the utility of adding extra context [1] with the original proposal, which From does not directly support.

Sometimes I wish for a standard trait-pair like From/Into that could serve this purpose in combination with ?, but I don't have any notion of what it might look like. My default in this situation is map_err.


  1. I mean application-specific context such as "... while checking capabilities". A message or other indicator that is irrelevant to the underlying operation that returned an error. ↩ī¸Ž

I find this method very clean :+1: