Hello, everyone. I believe my question may be similar to others in this forum; however, I'm having trouble understanding the answers and transferring them to my problem. Also it seems this all went way over my head to be able to grasp the actual problem and find a way to solve it.
I have the following compiling and working code. I have an async tauri command, which is supposed to call an async service function. This function should be called within a db transaction. Therefore I need to juggle between async and a thread, as diesel does not support async transactions.
#[tauri::command]
pub async fn some_tauri_command(data: &str, state: tauri::State<'_, AppState>) -> Result<(), String> {
let data = data.to_owned();
let mut c: PooledConnection<ConnectionManager<SqliteConnection>> = state.pool.get().unwrap();
let result_in_result: Result<Result<(), TxError>, tauri::Error> = tauri::async_runtime::spawn_blocking(move || {
let handle = tauri::async_runtime::TokioHandle::current();
let inner_result: Result<(), TxError> = c.transaction(|conn| {
handle.block_on(async {
SomeService::do_async(&data, conn)
.await
.map_err(|err| TxError::Other(err))
})
});
inner_result
}).await;
let flattened_result: Result<(), TxError> = result_in_result
.map_err(|err| TxError::Other(Box::new(err)))
.and_then(|res| res);
flattened_result.map_err(|err| err.to_string())
}
struct SomeService {}
impl SomeService {
pub async fn do_async(data: &String, conn: &mut SqliteConnection) -> Result<(), Box<dyn Error>> {
// do something with io (therefore async preferable), the db connection and data
Ok(())
}
}
and the error class which I need for diesel to be satisfied:
pub enum TxError {
Other(Box<dyn Error>),
DieselError(diesel::result::Error),
}
impl From<diesel::result::Error> for TxError {
fn from(value: diesel::result::Error) -> Self {
TxError::DieselError(value)
}
}
impl Debug for TxError {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
match self {
TxError::DieselError(err) => std::fmt::Debug::fmt(&err, f),
TxError::Other(err) => std::fmt::Debug::fmt(&err, f),
}
}
}
impl Display for TxError {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
match self {
TxError::DieselError(err) => std::fmt::Display::fmt(&err, f),
TxError::Other(err) => std::fmt::Display::fmt(&err, f),
}
}
}
impl Error for TxError {}
unsafe impl Send for TxError {}
As I need to do this in multiple commands, I was hoping to extract the thread-juggling to a utility function do_transactional
. Unfortunately, I cannot get this to work. Here is what I am trying:
#[tauri::command]
pub async fn some_tauri_command(data: &str, state: tauri::State<'_, AppState>) -> Result<(), String> {
let data = data.to_owned();
let pool: &Pool<ConnectionManager<SqliteConnection>> = &state.pool;
let result: Result<(), TxError> = do_transactional(pool, move |conn| {
SomeService::do_async(data, conn)
}).await;
result.map_err(|err| err.to_string())
}
struct SomeService {}
impl SomeService {
pub async fn do_async(data: String, conn: &mut SqliteConnection) -> Result<(), Box<dyn Error>> {
// do something with io (therefore async preferable), the db connection and data
Ok(())
}
}
pub async fn do_transactional<T, F, Fut>(pool: &Pool<ConnectionManager<SqliteConnection>>, f: F) -> Result<T, TxError>
where
T: Send + 'static,
F: (FnOnce(&mut SqliteConnection) -> Fut) + Send + 'static,
Fut: Future<Output=Result<T, Box<dyn Error>>>,
{
let mut c = pool.get().map_err(|err| TxError::Other(Box::new(err)))?;
let thread_result: Result<Result<T, TxError>, tauri::Error> = tauri::async_runtime::spawn_blocking(move || {
let handle = tauri::async_runtime::TokioHandle::current();
let transaction_result: Result<T, TxError> = c.transaction(|conn| {
let result: Result<T, Box<dyn Error>> = handle.block_on(async {
f(conn).await
});
result.map_err(|err| TxError::Other(err))
});
transaction_result
}).await;
thread_result
.map_err(|err| TxError::Other(Box::new(err)))
.and_then(|res| res)
}
which gives following error:
error: lifetime may not live long enough
--> src/commands/commands_commands.rs:40:9
|
38 | let result = do_transactional(pool, move |conn| {
| ----- return type of closure `impl futures::Future<Output = Result<(), Box<(dyn StdError + 'static)>>>` contains a lifetime `'2`
| |
| has type `&'1 mut SqliteConnection`
39 | let data = data.clone();
40 | SomeService::do_async(data, conn)
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ returning this value requires that `'1` must outlive `'2`
Apparently Rust wants conn
to live at least as long as the result from FnOnce. Why hasn't this been a problem in the working example from above, I don't understand what has changed in regards to the relation of their lifetimes. And obviously I would like to know how I could fix the issue.