I'm writing a GET method for a server application written in actix-web. LMDB is the database I use, its transactions need to be aborted or committed before the end of their lifetime.
In order to avoid a bunch of nested match
ing, I tried using map_err
on all of the functions that return a result. There I try to abort the transaction, but the transaction gets moved into the closure instead of being borrowed.
Is there any way to instead borrow the transaction into the closure, or do I have to bite the bullet and write a bunch of nested matches? Essentially, what's the most ergonomic way to write this function?
Example code (see comments next to txn.abort()
):
pub async fn get_user(db: Data<Database>, id: Identity) -> Result<Json<User>, Error> {
let username = id.identity().ok_or_else(|| error::ErrorUnauthorized(""))?;
let txn = db
.env
.begin_ro_txn()
.map_err(|_| error::ErrorInternalServerError(""))?;
let user_bytes = txn.get(db.handle_users, &username).map_err(|e| {
txn.abort(); // txn gets moved here
match e {
lmdb::Error::NotFound => {
id.forget();
error::ErrorUnauthorized("")
}
_ => error::ErrorInternalServerError(""),
}
})?;
let user: User = serde_cbor::from_slice(user_bytes).map_err(|_| {
txn.abort(); // cannot use txn here as is was moved
error::ErrorInternalServerError("")
})?;
txn.abort(); // cannot use txn here as is was moved
Ok(Json(user))
}
Sadly, in my case it is impossible to borrow the value into the closure, because abort
consumes the transaction. (Thanks to @vkurchatkin for explaining that)
In case anyone is interested, I've worked out a solution that satisfies me regardless of the issue. It was possible for me to avoid nesting a bunch of match
es.
I moved all of the logic that works on the transaction into a separate function and then delayed the evaluation of the function Result
until after running txn.abort()
(see comments):
pub async fn get_user(db: Data<Database>, id: Identity) -> Result<Json<User>, Error> {
let username = id.identity().ok_or_else(|| error::ErrorUnauthorized(""))?;
let txn = db
.env
.begin_ro_txn()
.map_err(|_| error::ErrorInternalServerError(""))?;
let user = db_get_user(&db, &txn, &id, &username); // Execute separate function but do not evaluate the function Result yet, notice missing question mark operator!
txn.abort(); // Abort the transaction after running the code. (Doesn't matter if it was successful or not. This consumes the transaction and it cannot be used anymore.)
Ok(Json(user?)) // Now evaluate the Result using the question mark operator.
}
// New separate function that uses the transaction.
fn db_get_user(
db: &Database,
txn: &RoTransaction,
id: &Identity,
username: &str,
) -> Result<User, Error> {
let user_bytes = txn.get(db.handle_users, &username).map_err(|e| match e {
lmdb::Error::NotFound => {
id.forget();
error::ErrorUnauthorized("")
}
_ => error::ErrorInternalServerError(""),
})?;
serde_cbor::from_slice(user_bytes).map_err(|_| error::ErrorInternalServerError(""))
}