I simplified a lot the code of my real project to narrowing down one issue I cannot understand how to fix.
Rust Explorer Playground here.
The code:
use std::{future::Future, pin::Pin, sync::Arc};
#[derive(Default)]
pub struct PlayerInput {
pub id: String,
pub name: String,
pub location: String,
// pub team: Box<Team>,
// others...
}
#[derive(Debug, sqlx::FromRow)]
pub struct DomainPlayer {
pub id: String,
pub name: String,
pub location: String,
pub shirt_number: i64,
// pub team: Box<Team>,
// others...
}
impl DomainPlayer {
fn new(id: &str, name: &str, location: &str, shirt_number: i64) -> Self {
Self {
id: id.to_string(),
name: name.to_string(),
location: location.to_string(),
shirt_number,
}
}
}
pub struct PlayerCreateLambdaArgs<'a> {
// other needed fields here
pub shirt_next_value: Box<
dyn FnOnce(&'a str) -> Pin<Box<dyn Future<Output = Result<i64, String>> + Send + 'a>>
+ Send
+ Sync
+ 'a,
>,
}
async fn player_create<'a>(
pool: Arc<sqlx::PgPool>,
_team_id: &'a str,
_input: &'a PlayerInput,
lambda: &(dyn for<'b> Fn(
PlayerCreateLambdaArgs<'b>,
) -> Pin<Box<dyn Future<Output = Result<DomainPlayer, String>> + Send + 'b>>
+ Sync
+ '_),
) -> Result<DomainPlayer, String> {
let mut tx = pool.begin().await.unwrap();
let domain_player = lambda(PlayerCreateLambdaArgs {
shirt_next_value: Box::new(|model: &str| {
Box::pin(shirt_get_next_and_increase(&mut tx, model))
}),
})
.await?;
let res =
sqlx::query_as::<_, DomainPlayer>("INSERT INTO player (...) VALUES (...) RETURNING *")
.bind(domain_player.id)
.bind(domain_player.shirt_number)
.fetch_one(&mut *tx)
.await
.unwrap();
Ok(res)
}
async fn shirt_get_next_and_increase(
tx: &mut sqlx::PgConnection,
model: &str,
) -> Result<i64, String> {
// Here I'm awaiting an async call for DB operations using the same DB transacion of the caller (_tx)...
dbg!(tx);
// use model here...
dbg!(model);
let res = 123;
Ok(res)
}
pub async fn handle(pool: Arc<sqlx::PgPool>, input: &PlayerInput) -> Result<DomainPlayer, String> {
let res = player_create(pool, "team", input, &|args| {
let input = input;
Box::pin(async move {
let shirt_number = (args.shirt_next_value)("player").await?;
let o = DomainPlayer::new(&input.id, &input.name, &input.location, shirt_number);
Ok(o)
})
})
.await?;
Ok(res)
}
#[tokio::main]
async fn main() -> Result<(), String> {
let pool = Arc::new(sqlx::PgPool::connect("fake_url").await.unwrap());
let new_player_input = PlayerInput {
id: "abc".to_string(),
name: "John".to_string(),
location: "Boston".to_string(),
};
let player = handle(pool.clone(), &new_player_input).await?;
dbg!(player);
Ok(())
}
The error:
error: lifetime may not live long enough
--> src/main.rs:104:9
|
100 | pub async fn handle(pool: Arc<sqlx::PgPool>, input: &PlayerInput) -> Result<DomainPlayer, String> {
| - let's call the lifetime of this reference `'1`
...
104 | / Box::pin(async move {
105 | | let shirt_number = (args.shirt_next_value)("player").await?;
106 | |
107 | | let o = DomainPlayer::new(&input.id, &input.name, &input.location, shirt_number);
108 | |
109 | | Ok(o)
110 | | })
| |__________^ returning this value requires that `'1` must outlive `'static`
Let's rewrite your handle a little bit, to make it easier to explain:
pub async fn handle(pool: Arc<sqlx::PgPool>, input: &PlayerInput) -> Result<DomainPlayer, String> {
let lambdaref: &(dyn for<'b> Fn(PlayerCreateLambdaArgs<'b>) -> Pin<Box<dyn Future<Output=Result<DomainPlayer, String>> + Send + 'b>> + Sync) = &|args: PlayerCreateLambdaArgs| {
let input = input;
Box::pin(async move {
let shirt_number = (args.shirt_next_value)("player").await?;
let o = DomainPlayer::new(&input.id, &input.name, &input.location, shirt_number);
Ok(o)
})
};
let res = player_create(pool, "team", input, lambdaref)
.await?;
Ok(res)
}
so lambdaref references a lambda, which takes a PlayerCreateLambdaArgs<'b> and produces a a boxed future guaranteed to live at least as long as 'b.
And it also captures input which we know lives at least as long as 'a.
So we can deduce, that 'a must live at least as long as 'b otherwise the boxed future couldn't live at least as long as 'b, as it needs access to input, too.
But 'b is not yet determined and can be chosen by the caller of the lamba. The caller of the lambda could choose to use a PlayerCreateLambdaArgs<'static> and therefore choose 'b to be 'static.
So rustc has to demand 'a to be 'static so the lambda can be called with every possible 'b.
If you can change player_create and PlayerCreateLambdaArgs, then you can
side step this issue. player_create already gets input and could just
hand it down to the lamba inside PlayerCreateLambdaArgs, too.
Like in https://www.rustexplorer.com/b/8vonk0
If you don't mind me saying, it looks all a bit convoluted, what is it, you really want to achieve?
Related
I'm using the below code in my real project. (Obviously this is simplified and one file only for playground.)
Rust explorer playground
As you can see I'm trying to use a sqlx Transaction from one function to a closure.
But I'm stucked.
I don't even know if this (so common Golang pattern) is the best wat to do in Rust. But at least it should work now.
use std::{future::Future, pin::Pin, sync::Arc};
pub trait Trait: Send + Sync + Player + Shirt {}
impl<T: Player + Shirt> Trait for T {}
pub type Lambda<'a, ArgT, ResT> =
dyn Fn(ArgT) -> Pin<Box<dyn Future<Output = Result<ResT, String>> + Send + 'a>> + Sync + 'a;
#[async_trait::async_trait]
pub trait Player: Send + Sync {
async fn player_create<'a>(
&'a self,
_input: &PlayerInput,
lambda: &Lambda<'_, PlayerCreateLambdaArgs<'a>, DomainPlayer>,
) -> Result<DomainPlayer, String>;
}
#[async_trait::async_trait]
pub trait Shirt: Send + Sync {
async fn shirt_get_next_and_increase<'a>(
&'a self,
tx: &'a mut sqlx::PgConnection,
model: String,
) -> Result<i64, String>;
}
pub struct Repo {
pub pool: Arc<sqlx::PgPool>,
}
impl Repo {
pub fn new(pool: Arc<sqlx::PgPool>) -> Self {
Self { pool }
}
}
#[async_trait::async_trait]
impl Player for Repo {
async fn player_create<'a>(
&'a self,
_input: &PlayerInput,
lambda: &Lambda<'_, PlayerCreateLambdaArgs<'a>, DomainPlayer>,
) -> Result<DomainPlayer, String> {
let mut tx = self.pool.begin().await.unwrap();
// use _input here
let shirt_next_value = Box::new(|model: String| {
self::Shirt::shirt_get_next_and_increase(self, &mut tx, model)
});
let domain_player = lambda(PlayerCreateLambdaArgs { shirt_next_value }).await?;
let res =
sqlx::query_as::<_, DomainPlayer>("INSERT INTO player (...) VALUES (...) RETURNING *")
.bind(domain_player.id)
.bind(domain_player.shirt_number)
.fetch_one(&mut *tx)
.await
.unwrap();
Ok(res)
}
}
#[async_trait::async_trait]
impl Shirt for Repo {
async fn shirt_get_next_and_increase<'a>(
&'a self,
_tx: &'a mut sqlx::PgConnection,
_model: String,
) -> Result<i64, String> {
// Here I'm awaiting an async call for DB operations using the same DB transacion of the caller (_tx)...
// use _tx here...
let res = 123;
Ok(res)
}
}
pub struct Needs {
pub command_pg_repo: Arc<dyn Trait>,
}
#[derive(Default)]
pub struct PlayerInput {
pub id: String,
}
#[derive(Debug, Default, Clone, sqlx::FromRow)]
pub struct DomainPlayer {
pub id: String,
pub shirt_number: i64,
}
pub struct PlayerCreateLambdaArgs<'a> {
// other needed fields here
pub shirt_next_value: Box<
dyn FnMut(String) -> Pin<Box<dyn Future<Output = Result<i64, String>> + Send + 'a>>
+ Send
+ Sync
+ 'a,
>,
}
pub struct Handler {
needs: Arc<Needs>,
}
impl Handler {
pub fn new(needs: Arc<Needs>) -> Self {
Self { needs }
}
pub async fn handle(&self, input: &PlayerInput) -> Result<DomainPlayer, String> {
let res = self
.needs
.command_pg_repo
.player_create(&input, &|mut args| {
let input = input;
Box::pin(async move {
let shirt_number = (args.shirt_next_value)("player".to_string()).await?;
let o = DomainPlayer {
id: input.id.to_string(),
shirt_number,
};
Ok(o)
})
})
.await?;
Ok(res)
}
}
#[tokio::main]
async fn main() -> Result<(), String> {
let db_conn = sqlx::PgPool::connect("fake_url").await.unwrap();
let pg_repo = Arc::new(Repo::new(Arc::new(db_conn)));
let needs = Arc::new(Needs {
command_pg_repo: pg_repo,
});
let handler = Handler::new(needs);
let new_player_input = PlayerInput {
id: "abc".to_string(),
};
let player = handler.handle(&new_player_input).await?;
dbg!(player);
Ok(())
}
The error:
error: captured variable cannot escape `FnMut` closure body
--> src/main.rs:62:13
|
57 | let mut tx = self.pool.begin().await.unwrap();
| ------ variable defined here
...
61 | let shirt_next_value = Box::new(|model: String| {
| - inferred to be a `FnMut` closure
62 | self::Shirt::shirt_get_next_and_increase(self, &mut tx, model)
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^--^^^^^^^^
| | |
| | variable captured here
| returns a reference to a captured variable which escapes the closure body
|
= note: `FnMut` closures only have access to their captured variables while they are executing...
= note: ...therefore, they cannot allow references to captured variables to escape
error[E0597]: `tx` does not live long enough
--> src/main.rs:62:65
|
55 | lambda: &Lambda<'_, PlayerCreateLambdaArgs<'a>, DomainPlayer>,
| ------ lifetime `'1` appears in the type of `lambda`
...
61 | let shirt_next_value = Box::new(|model: String| {
| --------------- value captured here
62 | self::Shirt::shirt_get_next_and_increase(self, &mut tx, model)
| ^^ borrowed value does not live long enough
...
65 | let domain_player = lambda(PlayerCreateLambdaArgs { shirt_next_value }).await?;
| ---------------- this usage requires that `tx` is borrowed for `'1`
...
76 | }
| - `tx` dropped here while still borrowed
error[E0499]: cannot borrow `tx` as mutable more than once at a time
--> src/main.rs:71:34
|
55 | lambda: &Lambda<'_, PlayerCreateLambdaArgs<'a>, DomainPlayer>,
| ------ lifetime `'1` appears in the type of `lambda`
...
61 | let shirt_next_value = Box::new(|model: String| {
| --------------- first mutable borrow occurs here
62 | self::Shirt::shirt_get_next_and_increase(self, &mut tx, model)
| -- first borrow occurs due to use of `tx` in closure
...
65 | let domain_player = lambda(PlayerCreateLambdaArgs { shirt_next_value }).await?;
| ---------------- this usage requires that `tx` is borrowed for `'1`
...
71 | .fetch_one(&mut *tx)
| ^^ second mutable borrow occurs here
Some errors have detailed explanations: E0499, E0597.
For more information about an error, try `rustc --explain E0499`.
There are 3 main issues with the code (more like 2 and a half):
Closures cannot return references to things the closure owns, or things the closure borrowed mutably (More formally: the return type of the Fn* function/closure family of traits cannot borrow from the closure type itself). One way to fix this is to have tx be moved into the closure, and then moved into the closure's returned Future. Shared ownership can be achieved with std::sync::Arc, and cloning it and moving it when you need to give another task ownership.
Fn closures may be called concurrently, so they cannot use mutable references to things the closure owns or has borrowed, but this code tries to use a &mut tx borrowing from the outer scope inside a Fn closure. (This issue is bypassed with the solution outlined below).
If you want to access something mutably when it is shared, you need to synchronize those accesses so only one thing can actually access it at a time. One way to do this in async Rust is using tokio::sync::Mutex.
Combining the above, putting tx into a Arc<Mutex<...>>, moving .clone()s of the Arc into the tasks that need ownership, and .lock().awaiting whenever you need to access it mutably may fix your problem (it makes it compile at least).
Rust explorer
diff --git a/src/main.rs b/src/main.rs
index 89fc611..6f7d375 100644
--- a/src/main.rs
+++ b/src/main.rs
## -1,5 +1,7 ##
use std::{future::Future, pin::Pin, sync::Arc};
+use tokio::sync::Mutex;
+
pub trait Trait: Send + Sync + Player + Shirt {}
impl<T: Player + Shirt> Trait for T {}
## -42,12 +44,19 ## impl Player for Repo {
_input: &PlayerInput,
lambda: &Lambda<'_, PlayerCreateLambdaArgs<'a>, DomainPlayer>,
) -> Result<DomainPlayer, String> {
- let mut tx = self.pool.begin().await.unwrap();
+ let tx = Arc::new(Mutex::new(self.pool.begin().await.unwrap()));
// use _input here
- let shirt_next_value = Box::new(|model: String| {
- self::Shirt::shirt_get_next_and_increase(self, &mut tx, model)
+ let shirt_next_value = Box::new({
+ let tx = tx.clone();
+ move |model: String| -> Pin<Box<dyn Future<Output = Result<i64, std::string::String>> + Send>> {
+ let tx = tx.clone();
+ Box::pin(async move {
+ self::Shirt::shirt_get_next_and_increase(self, &mut *tx.lock().await, model)
+ .await
+ })
+ }
});
let domain_player = lambda(PlayerCreateLambdaArgs { shirt_next_value }).await?;
## -56,7 +65,7 ## impl Player for Repo {
sqlx::query_as::<_, DomainPlayer>("INSERT INTO player (...) VALUES (...) RETURNING *")
.bind(domain_player.id)
.bind(domain_player.shirt_number)
- .fetch_one(&mut *tx)
+ .fetch_one(&mut *tx.lock().await)
.await
.unwrap();
Note that because a &mut PgConenction is passed to shirt_get_next_and_increase inside the async block, which references the MutexGuard<Transaction<...>> returned by tx.lock().await, the MutexGuard will be held until shirt_get_next_and_increase completes, even while it yeilds (if it yields). This shouldn't be an issue in this code, since it appears to be mostly sequential, and does not access tx until shirt_get_next_and_increase has completed. If this is not what you want, (i.e. if your actual code does access tx concurrently while shirt_get_next_and_increase is running) you could instead have shirt_get_next_and_increase take a &Mutex<Transaction<...>>, and only have it hold the lock when it needs to access the connection.
An sketch of an alternate solution would be to restructure the code so that the Transaction is passed around by value in function arguments and return values, e.g.
pub type Lambda<'a, ArgT, ResT> =
dyn Fn(ArgT) -> Pin<Box<dyn Future<Output = Result<(ResT, ArgT), String>> + Send + 'a>> + Sync + 'a;
// ...
async fn shirt_get_next_and_increase<'a>(
&'a self,
mut tx: sqlx::Transaction<'static, sqlx::PgConnection>,
_model: String,
) -> Result<(i64, sqlx::Transaction<'static, sqlx::PgConnection>), String> {
// ...
Ok((value, tx))
}
This could resolve the borrowing and shared-mutability issues, but may make the API more cumbersome, and may be infeasible for other reasons.
I'm trying to implement a StateMachine in Rust, but I encountered some problems while trying to fire the callback of StateMachine in a spawn thread.
Here is my StateMachine struct. The state is a generic T because I want to use it in many different scenerios, and I use a Vec to store all the callbacks those registered into this StateMachine.
At the very begining, I didn't use the lifetime 'a, but it will run into some lifetime problems, so I add the lifetime 'a by this suggestion: Idiomatic callbacks in Rust
pub struct StateMachine<'a, T> where T:Clone+Eq+'a {
state: RwLock<T>,
listeners2: Vec<Arc<Mutex<ListenerCallback<'a, T>>>>,
}
pub type ListenerCallback<'a, T> = dyn FnMut(T) -> Result<()> + Send + Sync + 'a ;
When the state is changed, the StateMachine will fire all the callbacks, as follows.
pub async fn try_set(&mut self, new_state:T) -> Result<()> {
if (block_on(self.state.read()).deref().eq(&new_state)) {
return Ok(())
}
// todo change the state
// fire every listener in spawn
let mut fire_results = vec![];
for listener in &mut self.listeners2 {
let state = new_state.clone();
let fire_listener = listener.clone();
fire_results.push(tokio::spawn(async move {
let mut guard = fire_listener.lock().unwrap();
guard.deref_mut()(state);
}));
}
// if fire result return Err, return it
for fire_result in fire_results {
fire_result.await?;
}
Ok(())
}
But it will cause a compilation error.
error[E0521]: borrowed data escapes outside of associated function
--> src/taf/taf-core/src/execution/state_machine.rs:54:33
|
15 | impl<'a,T> StateMachine<'a,T> where T:Clone+Eq+Send {
| -- lifetime `'a` defined here
...
34 | pub async fn try_set(&mut self, new_state:T) -> Result<()> {
| --------- `self` is a reference that is only valid in the associated function body
...
54 | let fire_listener = listener.clone();
| ^^^^^^^^^^^^^^^^
| |
| `self` escapes the associated function body here
| argument requires that `'a` must outlive `'static`
##########################################################
The full code is coupled with a lot of business logic, so I rewrite 2 demos as follows, the problems is the same. The first demo fire callback synchronously and it works, the second demo try to fire callback asynchronously, it encounter the same problem: self escapes the associated function body here.
First demo(it works):
use std::alloc::alloc;
use std::ops::DerefMut;
use std::sync::{Arc, Mutex, RwLock};
use anyhow::Result;
use dashmap::DashMap;
struct StateMachine<'a,T> where T:Clone+Eq+'a {
state: T,
listeners: Vec<Box<Callback<'a, T>>>,
}
type Callback<'a, T> = dyn FnMut(T) -> Result<()> + Send + Sync + 'a;
impl<'a, T> StateMachine<'a,T> where T:Clone+Eq+'a {
pub fn new(init_state: T) -> Self {
StateMachine {
state: init_state,
listeners: vec![]
}
}
pub fn add_listener(&mut self, listener: Box<Callback<'a, T>>) -> Result<()> {
self.listeners.push(listener);
Ok(())
}
pub fn set(&mut self, new_state: T) -> Result<()> {
self.state = new_state.clone();
for listener in &mut self.listeners {
listener(new_state.clone());
}
Ok(())
}
}
#[derive(Clone, Eq, PartialEq, Hash)]
enum ExeState {
Waiting,
Running,
Finished,
Failed,
}
struct Execution<'a> {
exec_id: String,
pub state_machine: StateMachine<'a, ExeState>,
}
struct ExecManager<'a> {
all_jobs: Arc<RwLock<DashMap<String, Execution<'a>>>>,
finished_jobs: Arc<RwLock<Vec<String>>>,
}
impl<'a> ExecManager<'a> {
pub fn new() -> Self {
ExecManager {
all_jobs: Arc::new(RwLock::new(DashMap::new())),
finished_jobs: Arc::new(RwLock::new(vec![]))
}
}
fn add_job(&mut self, job_id: String) {
let mut execution = Execution {
exec_id: job_id.clone(),
state_machine: StateMachine::new(ExeState::Waiting)
};
// add listener
let callback_finished_jobs = self.finished_jobs.clone();
let callback_job_id = job_id.clone();
execution.state_machine.add_listener( Box::new(move |new_state| {
println!("listener fired!, job_id {}", callback_job_id.clone());
if new_state == ExeState::Finished || new_state == ExeState::Failed {
let mut guard = callback_finished_jobs.write().unwrap();
guard.deref_mut().push(callback_job_id.clone());
}
Ok(())
}));
let mut guard = self.all_jobs.write().unwrap();
guard.deref_mut().insert(job_id, execution);
}
fn mock_exec(&mut self, job_id: String) {
let mut guard = self.all_jobs.write().unwrap();
let mut exec = guard.deref_mut().get_mut(&job_id).unwrap();
exec.state_machine.set(ExeState::Finished);
}
}
#[test]
fn test() {
let mut manager = ExecManager::new();
manager.add_job(String::from("job_id1"));
manager.add_job(String::from("job_id2"));
manager.mock_exec(String::from("job_id1"));
manager.mock_exec(String::from("job_id2"));
}
Second demo:
use std::alloc::alloc;
use std::ops::DerefMut;
use std::sync::{Arc, Mutex, RwLock};
use anyhow::Result;
use dashmap::DashMap;
use petgraph::algo::astar;
struct StateMachine<'a,T> where T:Clone+Eq+Send+'a {
state: T,
listeners: Vec<Arc<Mutex<Box<Callback<'a, T>>>>>,
}
type Callback<'a, T> = dyn FnMut(T) -> Result<()> + Send + Sync + 'a;
impl<'a, T> StateMachine<'a,T> where T:Clone+Eq+Send+'a {
pub fn new(init_state: T) -> Self {
StateMachine {
state: init_state,
listeners: vec![]
}
}
pub fn add_listener(&mut self, listener: Box<Callback<'a, T>>) -> Result<()> {
self.listeners.push(Arc::new(Mutex::new(listener)));
Ok(())
}
pub fn set(&mut self, new_state: T) -> Result<()> {
self.state = new_state.clone();
for listener in &mut self.listeners {
let spawn_listener = listener.clone();
tokio::spawn(async move {
let mut guard = spawn_listener.lock().unwrap();
guard.deref_mut()(new_state.clone());
});
}
Ok(())
}
}
#[derive(Clone, Eq, PartialEq, Hash)]
enum ExeState {
Waiting,
Running,
Finished,
Failed,
}
struct Execution<'a> {
exec_id: String,
pub state_machine: StateMachine<'a, ExeState>,
}
struct ExecManager<'a> {
all_jobs: Arc<RwLock<DashMap<String, Execution<'a>>>>,
finished_jobs: Arc<RwLock<Vec<String>>>,
}
impl<'a> ExecManager<'a> {
pub fn new() -> Self {
ExecManager {
all_jobs: Arc::new(RwLock::new(DashMap::new())),
finished_jobs: Arc::new(RwLock::new(vec![]))
}
}
fn add_job(&mut self, job_id: String) {
let mut execution = Execution {
exec_id: job_id.clone(),
state_machine: StateMachine::new(ExeState::Waiting)
};
// add listener
let callback_finished_jobs = self.finished_jobs.clone();
let callback_job_id = job_id.clone();
execution.state_machine.add_listener( Box::new(move |new_state| {
println!("listener fired!, job_id {}", callback_job_id.clone());
if new_state == ExeState::Finished || new_state == ExeState::Failed {
let mut guard = callback_finished_jobs.write().unwrap();
guard.deref_mut().push(callback_job_id.clone());
}
Ok(())
}));
let mut guard = self.all_jobs.write().unwrap();
guard.deref_mut().insert(job_id, execution);
}
fn mock_exec(&mut self, job_id: String) {
let mut guard = self.all_jobs.write().unwrap();
let mut exec = guard.deref_mut().get_mut(&job_id).unwrap();
exec.state_machine.set(ExeState::Finished);
}
}
#[test]
fn test() {
let mut manager = ExecManager::new();
manager.add_job(String::from("job_id1"));
manager.add_job(String::from("job_id2"));
manager.mock_exec(String::from("job_id1"));
manager.mock_exec(String::from("job_id2"));
}
Compile error of second demo:
error[E0521]: borrowed data escapes outside of associated function
--> generic/src/callback2.rs:34:34
|
15 | impl<'a, T> StateMachine<'a,T> where T:Clone+Eq+Send+'a {
| -- lifetime `'a` defined here
...
29 | pub fn set(&mut self, new_state: T) -> Result<()> {
| --------- `self` is a reference that is only valid in the associated function body
...
34 | let spawn_listener = listener.clone();
| ^^^^^^^^^^^^^^^^
| |
| `self` escapes the associated function body here
| argument requires that `'a` must outlive `'static`
|
= note: requirement occurs because of the type `std::sync::Mutex<Box<dyn FnMut(T) -> Result<(), anyhow::Error> + Send + Sync>>`, which makes the generic argument `Box<dyn FnMut(T) -> Result<(), anyhow::Error> + Send + Sync>` invariant
= note: the struct `std::sync::Mutex<T>` is invariant over the parameter `T`
= help: see <https://doc.rust-lang.org/nomicon/subtyping.html> for more information about variance
Tasks spawned with tokio::spawn() cannot use borrowed data (here, the data with lifetime 'a, whatever it may be). This is because there is not currently (and likely will never be) any way to guarantee that the borrowed data reliably outlives the spawned task.
You have two choices:
Fire the notifications without spawning. You can put the notification futures into a FuturesUnordered to run them all concurrently, but they will still all have to finish before try_set() does.
Remove the lifetime parameter; stop allowing callbacks that borrow data. Put 'static on your dyn types where necessary. Change the users of the StateMachine so they do not try to use borrowed data but use Arc instead, if necessary.
pub struct StateMachine<T> where T: Clone + Eq + 'static {
state: RwLock<T>,
listeners2: Vec<Arc<Mutex<ListenerCallback<T>>>>,
}
pub type ListenerCallback<T> = dyn FnMut(T) -> Result<()> + Send + Sync + 'static;
I'm trying the below code but I cannot understand how to fix the error, can you help me?
RUST PLAYGROUND DEMO: https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=977a9e7a5bb503e900818b8ff67daf7e
Code:
use std::{future::Future, pin::Pin, sync::Arc};
#[async_trait::async_trait]
pub trait PlayerTrait: Send + Sync {
async fn player_update<'a>(
&'a self,
input: PlayerInput,
lambda: &(dyn Fn(
PlayerLambdaArgs,
) -> Pin<Box<dyn Future<Output = Result<Player, String>> + Send + 'a>>
+ Sync),
) -> Result<Player, String>;
}
#[derive(Debug)]
pub struct Player {
pub name: String,
}
impl Player {
pub fn new(name: &str, team_id: i64) -> Result<Player, String> {
if team_id > 100 {
return Err("Sorry, this team is closed".to_string());
}
Ok(Player {
name: name.to_string(),
})
}
}
pub struct PlayerInput {
pub name: String,
}
pub struct PlayerLambdaArgs {
pub random_team_id: i64,
}
pub struct DBRepo {
pub db: String,
}
impl DBRepo {
pub fn new(db: String) -> Self {
Self { db }
}
}
#[async_trait::async_trait]
impl PlayerTrait for DBRepo {
async fn player_update<'a>(
&'a self,
_input: PlayerInput,
lambda: &(dyn Fn(
PlayerLambdaArgs,
) -> Pin<Box<dyn Future<Output = Result<Player, String>> + Send + 'a>>
+ Sync),
) -> Result<Player, String> {
// get from DB here
let random_team_id = 123;
let lambda_args = PlayerLambdaArgs { random_team_id };
let res = lambda(lambda_args).await?;
// do something with res here
Ok(Player { name: res.name })
}
}
#[tokio::main]
async fn main() {
let db_repo = Arc::new(DBRepo::new(String::from("hello")));
let input = PlayerInput {
name: "Bob".to_string(),
};
let player = db_repo
.player_update(input, &|args| {
Box::pin(async {
let player = Player::new(&input.name, args.random_team_id)?;
Ok(player)
})
})
.await;
dbg!(player);
}
Error:
Compiling playground v0.0.1 (/playground)
error[E0373]: async block may outlive the current function, but it borrows `args.random_team_id`, which is owned by the current function
--> src/main.rs:82:28
|
82 | Box::pin(async {
| ____________________________^
83 | | let player = Player::new(&input.name, args.random_team_id)?;
| | ------------------- `args.random_team_id` is borrowed here
84 | |
85 | | Ok(player)
86 | | })
| |_____________^ may outlive borrowed value `args.random_team_id`
|
note: async block is returned here
--> src/main.rs:82:13
|
82 | / Box::pin(async {
83 | | let player = Player::new(&input.name, args.random_team_id)?;
84 | |
85 | | Ok(player)
86 | | })
| |______________^
help: to force the async block to take ownership of `args.random_team_id` (and any other referenced variables), use the `move` keyword
|
82 | Box::pin(async move {
| ++++
For more information about this error, try `rustc --explain E0373`.
error: could not compile `playground` due to previous error
So, pretty much always, async closures must also be move. This also means that any closure they're inside must be move as well.
So the first thing to do is add move:
#[tokio::main]
async fn main() {
let db_repo = Arc::new(DBRepo::new(String::from("hello")));
let input = PlayerInput {
name: "Bob".to_string(),
};
let player = db_repo
.player_update(input, &(move |args| {
Box::pin(async move {
let player = Player::new(&input.name, args.random_team_id)?;
Ok(player)
})
}))
.await;
dbg!(player);
}
We're still getting some errors here around input.name. This is because we're trying to move input as an argument to player_update and as a capture of the closure and the async block.
So let's add a clone for the PlayerInput construction:
#[tokio::main]
async fn main() {
let db_repo = Arc::new(DBRepo::new(String::from("hello")));
let name = "Bob".to_string();
let input = PlayerInput {
name: name.clone(),
};
let player = db_repo
.player_update(input, &(move |args| {
Box::pin(async move {
let player = Player::new(&name, args.random_team_id)?;
Ok(player)
})
}))
.await;
dbg!(player);
}
This leads to one final move error. We can fix this by taking a reference to name outside the closure, instead of inside it:
#[tokio::main]
async fn main() {
let db_repo = Arc::new(DBRepo::new(String::from("hello")));
let name = "Bob".to_string();
let name = &name;
let input = PlayerInput {
name: name.clone(),
};
let player = db_repo
.player_update(input, &(move |args| {
Box::pin(async move {
let player = Player::new(name, args.random_team_id)?;
Ok(player)
})
}))
.await;
dbg!(player);
}
playground
Hi I try to pass my closure to mockall crate returning function in following way:
pub fn set_dialog_game_selection(dialogs: &mut Box<MockAsk>, steam_id: String) {
dialogs
.expect_ask_for_game_decision_if_needed_and_set_game_to_launch()
.returning(ask_for_game_decision_if_needed_return_mock(steam_id));
}
pub fn ask_for_game_decision_if_needed_return_mock<'x, 'y>(
steam_id: String,
) -> Box<dyn Fn(&'x mut REvilConfig, &'y mut REvilManagerState) -> ResultDialogsErr<()> + Send> {
let default = move |_: &'x mut REvilConfig, state: &'y mut REvilManagerState| {
state.selected_game_to_launch = Some(steam_id.clone());
Ok(())
};
return Box::new(default);
}
but I get
error[E0308]: mismatched types
--> src\tests\integration.rs:128:14
|
128 | .returning(ask_for_game_decision_if_needed_return_mock(steam_id.to_string()));
| ^^^^^^^^^ lifetime mismatch
|
= note: expected associated type `<dyn Fn(&mut configStruct::REvilConfig, &mut rManager_header::REvilManagerState) -> Result<(), error_stack::Report<DialogsErrors>> + Send as FnOnce<(&mut configStruct::REvilConfig, &mut rManager_header::REvilManagerState)>>::Output`
found associated type `<dyn Fn(&mut configStruct::REvilConfig, &mut rManager_header::REvilManagerState) -> Result<(), error_stack::Report<DialogsErrors>> + Send as FnOnce<(&mut configStruct::REvilConfig, &mut rManager_header::REvilManagerState)>>::Output`
note: the lifetime requirement is introduced here
--> src\dialogs\dialogs.rs:54:10
|
54 | ) -> ResultDialogsErr<()>;
| ^^^^^^^^^^^^^^^^^^^^
P.S I'm writting mod manager for Resident Evil game that's why "REvil" :p
P.S2 In the end I managed to rewrite it like:
pub fn set_dialog_game_selection(dialogs: &mut Box<MockAsk>, steam_id: String) {
dialogs
.expect_ask_for_game_decision_if_needed_and_set_game_to_launch()
.returning(move |_, state| {
set_game_decision(steam_id.clone(), state);
Ok(())
});
}
pub fn set_game_decision(steam_id: String, state: &mut REvilManagerState) {
state.selected_game_to_launch = Some(steam_id);
}
But why my first approach doeasn't work? :(
Function signature I'm trying to mock is as follow:
pub type ResultDialogsErr<T> = Result<T, DialogsErrors>;
fn ask_for_game_decision_if_needed_and_set_game_to_launch(
&mut self,
config: &mut REvilConfig,
state: &mut REvilManagerState,
) -> ResultDialogsErr<()>;
If you remove the manual lifetime annotations, it works:
pub fn ask_for_game_decision_if_needed_return_mock(
steam_id: String,
) -> Box<dyn Fn(&mut REvilConfig, &mut REvilManagerState) -> DynResult<()> + Send> {
let default = move |_: &mut REvilConfig, state: &mut REvilManagerState| {
state.selected_game_to_launch = Some(steam_id.clone());
Ok(())
};
return Box::new(default);
}
I am trying to do simple telegram bot. I have to response some answer for provided questions.
Problem is that I can't use borrowed parts of question (string) to pass through it to db save function.
I've cut my code as mush as possible:
pub enum Answer {
DbCommand(Box<dyn Fn()>),
}
pub fn process(question: &str) -> Answer {
let parts: Vec<&str> = question
.split(" ")
.collect();
let channel = parts.get(1).unwrap();
Answer::DbCommand(Box::new(|| {
save_to_db(channel)
}))
}
pub fn save_to_db(chan: &str) {
// Saving to db
}
Playground
Output is:
error[E0621]: explicit lifetime required in the type of `question`
--> src/lib.rs:12:23
|
5 | pub fn process(question: &str) -> Answer {
| ---- help: add explicit lifetime `'static` to the type of `question`: `&'static str`
...
12 | Answer::DbCommand(Box::new(|| {
| _______________________^
13 | | save_to_db(channel)
14 | | }))
| |______^ lifetime `'static` required
If I add some function lifetime, then I get error E0495. There's not much information on it
split does not allocate anything, it only iterates over the initial string, keeping a reference to it. You need to own the string and move it into the closure:
pub enum Answer {
DbCommand(Box<dyn Fn()>),
}
pub fn process(question: &str) -> Answer {
let channel = question.split(" ").nth(1).unwrap().to_owned();
Answer::DbCommand(Box::new(move || save_to_db(&channel)))
}
pub fn save_to_db(chan: &str) {
// Saving to db
}
By the way, you do not need to collect anything in this case.
If you really don't want to allocate a string, you can make your struct generic over a lifetime, but I think that this would add an unneeded complexity.:
pub enum Answer<'a> {
DbCommand(Box<dyn Fn() + 'a>),
}
pub fn process(question: &str) -> Answer {
let channel = question.split(" ").nth(1).unwrap();
Answer::DbCommand(Box::new(move || save_to_db(channel)))
}
pub fn save_to_db(chan: &str) {
// Saving to db
}
That's because a trait object has an implicit 'static lifetime by default.
My final code looks like this:
pub enum Answer<'a> {
Text(String),
DbCommand(Box<dyn Fn() -> Result<String, Error> + 'a>),
}
pub fn process(question: &str) -> Answer {
let mut parts = question
.split(" ")
.map(str::trim)
.filter(|s| !s.is_empty());
let command = parts.next();
match command {
//...
Some("/subscribe") => {
match parts.next() {
Some(channel) => {
Answer::DbCommand(Box::new(move || {
db::subscribe_to_channel(&channel)
//...
}))
},
None => Answer::Text("Provide channel name".into()),
}
},
_ => Answer::Text("Invalid command.".into()),
}
}