so I have a personal problem. a problem that can only be solved by passing functions into functions.
here I am going to document the iterations through which this code (d)evolved as code requirements elsewhere changed.
So the original problem is thus: I had some code that needed to coordinate two clients interacting with eachother. the primary structure was a
the way in which I wanted to interact with this was that I would give it the `ClientId` of one client and it would then fetch for me the coresponding `other_client` and give me mutable references to both. and given the aforementioned personal problem, I figured I would solve this through passing a function to this structure:
and this was great, perfect, ideal. until suddenly `F` needed to be async.
now this error totally makes sense. `state` is lending out `this` and `other` which get captured by this async block which may not be `.await`ed until after `state` stops existing.
lets throw some lifetimes in here and see if it helps:
throwing shit at the wall, maybe a second lifetime
its at this point that I notice all except one of my actual-code-that-caused-me-to-write-this `with` calls were using an async block internally. maybe I can just make `with` async and move on with my life
so how do I make my resulting borrowed clients live longer than this function? idk lets try
I'm starting to run out of ideas at this point.a
in a questionable state of mind I figure "hey what happens if I send the `RefMut`s just directly by value?"
anyway, it seems the core problems I am having is that references to outside data inside async closures is just not gonna work.so lets try some stupid shit and see which I hate least.
removing from hashmap to run the function then re-add after:
ok so references here are infinite forever suffering how about I just pass the state by value and try and make that work in some capacity. so to pass by value then I need to like return the data back out:
so after some googling I find a possibly promising `FutureBoxLocal` future where I can specify a lifetime lets see how that goes
you know just for completions sake lets just do the
here I am going to document the iterations through which this code (d)evolved as code requirements elsewhere changed.
So the original problem is thus: I had some code that needed to coordinate two clients interacting with eachother. the primary structure was a
HashMap
.the way in which I wanted to interact with this was that I would give it the `ClientId` of one client and it would then fetch for me the coresponding `other_client` and give me mutable references to both. and given the aforementioned personal problem, I figured I would solve this through passing a function to this structure:
use std::collections::HashMap; #[derive(Copy, Clone, Hash, Eq, PartialEq)] struct ClientId(u32); struct ClientState { other_client: ClientId, some_data: u32, } #[derive(Debug)] enum StateError { Blah, } #[derive(Default)] struct State { state: HashMap<ClientId, ClientState>, } impl State { pub fn new_client(&mut self, client1: ClientId, client2: ClientId) { self.state.insert(client1, ClientState { other_client: client2, some_data: 0, }); self.state.insert(client2, ClientState { other_client: client1, some_data: 0, }); } pub fn with<T, F>(&mut self, client: ClientId, func: F) -> Result<T, StateError> where F: Fn(&mut ClientState, &mut ClientState) -> T { let c1 = self.state.get_mut(&client).ok_or(StateError::Blah)?; let c2 = self.state.get_mut(&c1.other_client).ok_or(StateError::Blah)?; Ok(func(c1, c2)) } } fn main() { let mut state = State::default(); state.new_client(ClientId(1), ClientId(2)); state.with(ClientId(1), |this, other| { this.some_data = 23; other.some_data = 5; }).unwrap(); println!("c1 {}", state.state.get(&ClientId(1)).unwrap().borrow().some_data); println!("c2 {}", state.state.get(&ClientId(2)).unwrap().borrow().some_data); }cool great got the basic idea going lets see how it works
error[E0499]: cannot borrow `self.state` as mutable more than once at a time --> src/main.rs:37:18 | 36 | let c1 = self.state.get_mut(&client).ok_or(StateError::Blah)?; | --------------------------- first mutable borrow occurs here 37 | let c2 = self.state.get_mut(&c1.other_client).ok_or(StateError::Blah)?; | ^^^^^^^^^^^^^^^^^^^----------------^ | | | | | first borrow later used here | second mutable borrow occurs herejokes on me of course it doesn't work those there consecutive `get_mut`s are just not gonna work luckily we have this rad `RefCell` thing to put in there to make this work:
use std::collections::HashMap; use std::cell::RefCell; #[derive(Copy, Clone, Hash, Eq, PartialEq)] struct ClientId(u32); struct ClientState { other_client: ClientId, some_data: u32, } #[derive(Debug)] enum StateError { Blah, } #[derive(Default)] struct State { state: HashMap<ClientId, RefCell<ClientState>>, } impl State { pub fn new_client(&mut self, client1: ClientId, client2: ClientId) { self.state.insert(client1, RefCell::new(ClientState { other_client: client2, some_data: 0, })); self.state.insert(client2, RefCell::new(ClientState { other_client: client1, some_data: 0, })); } pub fn with<T, F>(&mut self, client: ClientId, func: F) -> Result<T, StateError> where F: Fn(&mut ClientState, &mut ClientState) -> T { let mut c1 = self.state.get(&client).ok_or(StateError::Blah)?.borrow_mut(); let mut c2 = self.state.get(&c1.other_client).ok_or(StateError::Blah)?.borrow_mut(); Ok(func(&mut *c1, &mut *c2)) } } fn main() { let mut state = State::default(); state.new_client(ClientId(1), ClientId(2)); state.with(ClientId(1), |this, other| { this.some_data = 23; other.some_data = 5; }).unwrap(); println!("c1 {}", state.state.get(&ClientId(1)).unwrap().borrow().some_data); println!("c2 {}", state.state.get(&ClientId(2)).unwrap().borrow().some_data); }
$ cargo run Compiling sufferingwithwith v0.1.0 (/home/jake/projects/sharnoth.com/0005) Finished dev [unoptimized + debuginfo] target(s) in 0.19s Running `target/debug/sufferingwithwith` c1 23 c2 5look at it go.
and this was great, perfect, ideal. until suddenly `F` needed to be async.
use std::collections::HashMap; use std::cell::RefCell; #[derive(Copy, Clone, Hash, Eq, PartialEq)] struct ClientId(u32); struct ClientState { other_client: ClientId, some_data: u32, } #[derive(Debug)] enum StateError { Blah, } #[derive(Default)] struct State { state: HashMap<ClientId, RefCell<ClientState>>, } impl State { pub fn new_client(&mut self, client1: ClientId, client2: ClientId) { self.state.insert(client1, RefCell::new(ClientState { other_client: client2, some_data: 0, })); self.state.insert(client2, RefCell::new(ClientState { other_client: client1, some_data: 0, })); } pub fn with<T, F>(&mut self, client: ClientId, func: F) -> Result<T, StateError> where F: Fn(&mut ClientState, &mut ClientState) -> T { let mut c1 = self.state.get(&client).ok_or(StateError::Blah)?.borrow_mut(); let mut c2 = self.state.get(&c1.other_client).ok_or(StateError::Blah)?.borrow_mut(); Ok(func(&mut *c1, &mut *c2)) } } #[async_std::main] async fn main() { let mut state = State::default(); state.new_client(ClientId(1), ClientId(2)); state.with(ClientId(1), |this, other| async { this.some_data = 23; other.some_data = 5; }).unwrap().await; println!("c1 {}", state.state.get(&ClientId(1)).unwrap().borrow().some_data); println!("c2 {}", state.state.get(&ClientId(2)).unwrap().borrow().some_data); }
error: lifetime may not live long enough --> src/main.rs:50:43 | 50 | state.with(ClientId(1), |this, other| async { | ______________________________----_______-_^ | | | | | | | return type of closure `impl Futureand now the suffering begins
now this error totally makes sense. `state` is lending out `this` and `other` which get captured by this async block which may not be `.await`ed until after `state` stops existing.
lets throw some lifetimes in here and see if it helps:
use std::collections::HashMap; use std::cell::RefCell; #[derive(Copy, Clone, Hash, Eq, PartialEq)] struct ClientId(u32); struct ClientState { other_client: ClientId, some_data: u32, } #[derive(Debug)] enum StateError { Blah, } #[derive(Default)] struct State { state: HashMap<ClientId, RefCell<ClientState>>, } impl State { pub fn new_client(&mut self, client1: ClientId, client2: ClientId) { self.state.insert(client1, RefCell::new(ClientState { other_client: client2, some_data: 0, })); self.state.insert(client2, RefCell::new(ClientState { other_client: client1, some_data: 0, })); } pub fn with<'a, T, F>(&'a mut self, client: ClientId, func: F) -> Result<T, StateError> where F: Fn(&'a mut ClientState, &'a mut ClientState) -> T { let mut c1 = self.state.get(&client).ok_or(StateError::Blah)?.borrow_mut(); let mut c2 = self.state.get(&c1.other_client).ok_or(StateError::Blah)?.borrow_mut(); Ok(func(&mut *c1, &mut *c2)) } } #[async_std::main] async fn main() { let mut state = State::default(); state.new_client(ClientId(1), ClientId(2)); state.with(ClientId(1), |this, other| async { this.some_data = 23; other.some_data = 5; }).unwrap().await; println!("c1 {}", state.state.get(&ClientId(1)).unwrap().borrow().some_data); println!("c2 {}", state.state.get(&ClientId(2)).unwrap().borrow().some_data); }
error[E0597]: `c1` does not live long enough --> src/main.rs:41:23 | 34 | pub fn with<'a, T, F>(&'a mut self, client: ClientId, func: F) -> Resultliterally useless| -- lifetime `'a` defined here ... 41 | Ok(func(&mut *c1, &mut *c2)) | -----------^^----------- | | | | | borrowed value does not live long enough | argument requires that `c1` is borrowed for `'a` 42 | } | - `c1` dropped here while still borrowed error[E0597]: `c2` does not live long enough --> src/main.rs:41:33 | 34 | pub fn with<'a, T, F>(&'a mut self, client: ClientId, func: F) -> Result | -- lifetime `'a` defined here ... 41 | Ok(func(&mut *c1, &mut *c2)) | ---------------------^^- | | | | | borrowed value does not live long enough | argument requires that `c2` is borrowed for `'a` 42 | } | - `c2` dropped here while still borrowed
throwing shit at the wall, maybe a second lifetime
'b
that is less than 'a
:use std::collections::HashMap; use std::cell::RefCell; #[derive(Copy, Clone, Hash, Eq, PartialEq)] struct ClientId(u32); struct ClientState { other_client: ClientId, some_data: u32, } #[derive(Debug)] enum StateError { Blah, } #[derive(Default)] struct State { state: HashMap<ClientId, RefCell<ClientState>>, } impl State { pub fn new_client(&mut self, client1: ClientId, client2: ClientId) { self.state.insert(client1, RefCell::new(ClientState { other_client: client2, some_data: 0, })); self.state.insert(client2, RefCell::new(ClientState { other_client: client1, some_data: 0, })); } pub fn with<'a, 'b, T, F>(&'a mut self, client: ClientId, func: F) -> Result<T, StateError> where 'a: 'b, F: Fn(&'b mut ClientState, &'b mut ClientState) -> T { let mut c1 = self.state.get(&client).ok_or(StateError::Blah)?.borrow_mut(); let mut c2 = self.state.get(&c1.other_client).ok_or(StateError::Blah)?.borrow_mut(); Ok(func(&mut *c1, &mut *c2)) } } #[async_std::main] async fn main() { let mut state = State::default(); state.new_client(ClientId(1), ClientId(2)); state.with(ClientId(1), |this, other| async { this.some_data = 23; other.some_data = 5; }).unwrap().await; println!("c1 {}", state.state.get(&ClientId(1)).unwrap().borrow().some_data); println!("c2 {}", state.state.get(&ClientId(2)).unwrap().borrow().some_data); }
error[E0597]: `c1` does not live long enough --> src/main.rs:42:23 | 34 | pub fn with<'a, 'b, T, F>(&'a mut self, client: ClientId, func: F) -> Resultwhy did I think that would do anything.| -- lifetime `'b` defined here ... 42 | Ok(func(&mut *c1, &mut *c2)) | -----------^^----------- | | | | | borrowed value does not live long enough | argument requires that `c1` is borrowed for `'b` 43 | } | - `c1` dropped here while still borrowed error[E0597]: `c2` does not live long enough --> src/main.rs:42:33 | 34 | pub fn with<'a, 'b, T, F>(&'a mut self, client: ClientId, func: F) -> Result | -- lifetime `'b` defined here ... 42 | Ok(func(&mut *c1, &mut *c2)) | ---------------------^^- | | | | | borrowed value does not live long enough | argument requires that `c2` is borrowed for `'b` 43 | } | - `c2` dropped here while still borrowed
its at this point that I notice all except one of my actual-code-that-caused-me-to-write-this `with` calls were using an async block internally. maybe I can just make `with` async and move on with my life
use std::collections::HashMap; use std::cell::RefCell; use std::future::Future; #[derive(Copy, Clone, Hash, Eq, PartialEq)] struct ClientId(u32); struct ClientState { other_client: ClientId, some_data: u32, } #[derive(Debug)] enum StateError { Blah, } #[derive(Default)] struct State { state: HashMap<ClientId, RefCell<ClientState>>, } impl State { pub fn new_client(&mut self, client1: ClientId, client2: ClientId) { self.state.insert(client1, RefCell::new(ClientState { other_client: client2, some_data: 0, })); self.state.insert(client2, RefCell::new(ClientState { other_client: client1, some_data: 0, })); } pub async fn with<'a, T, F, Fut>(&'a mut self, client: ClientId, func: F) -> Result<T, StateError> where F: Fn(&'a mut ClientState, &'a mut ClientState) -> Fut, Fut: Future<Output=T>, { let mut c1 = self.state.get(&client).ok_or(StateError::Blah)?.borrow_mut(); let mut c2 = self.state.get(&c1.other_client).ok_or(StateError::Blah)?.borrow_mut(); Ok(func(&mut *c1, &mut *c2).await) } } #[async_std::main] async fn main() { let mut state = State::default(); state.new_client(ClientId(1), ClientId(2)); state.with(ClientId(1), |this, other| async { this.some_data = 23; other.some_data = 5; }).await.unwrap(); println!("c1 {}", state.state.get(&ClientId(1)).unwrap().borrow().some_data); println!("c2 {}", state.state.get(&ClientId(2)).unwrap().borrow().some_data); }
error[E0597]: `c1` does not live long enough --> src/main.rs:43:23 | 35 | pub async fn with<'a, T, F, Fut>(&'a mut self, client: ClientId, func: F) -> ResultI suppose if I actually sat to think about this before just throwing code at my computer I would have realized this would never have solved anything. the result is still a future that needs to be `.await`ed outside of this function scope.| -- lifetime `'a` defined here ... 43 | Ok(func(&mut *c1, &mut *c2).await) | -----------^^----------- | | | | | borrowed value does not live long enough | argument requires that `c1` is borrowed for `'a` 44 | } | - `c1` dropped here while still borrowed error[E0597]: `c2` does not live long enough --> src/main.rs:43:33 | 35 | pub async fn with<'a, T, F, Fut>(&'a mut self, client: ClientId, func: F) -> Result | -- lifetime `'a` defined here ... 43 | Ok(func(&mut *c1, &mut *c2).await) | ---------------------^^- | | | | | borrowed value does not live long enough | argument requires that `c2` is borrowed for `'a` 44 | } | - `c2` dropped here while still borrowed
so how do I make my resulting borrowed clients live longer than this function? idk lets try
Arc
I'm getting desperateuse std::collections::HashMap; use std::cell::RefCell; use std::future::Future; use async_std::sync::Arc; #[derive(Copy, Clone, Hash, Eq, PartialEq)] struct ClientId(u32); struct ClientState { other_client: ClientId, some_data: u32, } #[derive(Debug)] enum StateError { Blah, } #[derive(Default)] struct State { state: HashMap<ClientId, Arc<RefCell<ClientState>>>, } impl State { pub fn new_client(&mut self, client1: ClientId, client2: ClientId) { self.state.insert(client1, Arc::new(RefCell::new(ClientState { other_client: client2, some_data: 0, }))); self.state.insert(client2, Arc::new(RefCell::new(ClientState { other_client: client1, some_data: 0, }))); } pub async fn with<'a, T, F, Fut>(&'a mut self, client: ClientId, func: F) -> Result<T, StateError> where F: Fn(&'a mut ClientState, &'a mut ClientState) -> Fut, Fut: Future<Output=T>, { let c1 = self.state.get(&client).ok_or(StateError::Blah)?.clone(); let c2 = self.state.get(&c1.borrow().other_client).ok_or(StateError::Blah)?.clone(); let mut c1b = c1.borrow_mut(); let mut c2b = c2.borrow_mut(); Ok(func(&mut *c1b, &mut *c2b).await) } } #[async_std::main] async fn main() { let mut state = State::default(); state.new_client(ClientId(1), ClientId(2)); state.with(ClientId(1), |this, other| async { this.some_data = 23; other.some_data = 5; }).await.unwrap(); println!("c1 {}", state.state.get(&ClientId(1)).unwrap().borrow().some_data); println!("c2 {}", state.state.get(&ClientId(2)).unwrap().borrow().some_data); }
error[E0597]: `c1` does not live long enough --> src/main.rs:45:23 | 37 | pub async fn with<'a, T, F, Fut>(&'a mut self, client: ClientId, func: F) -> Resultyeah the same problem the borrowing of the refcell does not last long enough for the actual running of the async function.| -- lifetime `'a` defined here ... 45 | let mut c1b = c1.borrow_mut(); | ^^^^^^^^^^^^^^^ borrowed value does not live long enough ... 48 | Ok(func(&mut *c1b, &mut *c2b).await) | -------------------------- argument requires that `c1` is borrowed for `'a` 49 | } | - `c1` dropped here while still borrowed error[E0597]: `c2` does not live long enough --> src/main.rs:46:23 | 37 | pub async fn with<'a, T, F, Fut>(&'a mut self, client: ClientId, func: F) -> Result | -- lifetime `'a` defined here ... 46 | let mut c2b = c2.borrow_mut(); | ^^^^^^^^^^^^^^^ borrowed value does not live long enough 47 | 48 | Ok(func(&mut *c1b, &mut *c2b).await) | -------------------------- argument requires that `c2` is borrowed for `'a` 49 | } | - `c2` dropped here while still borrowed error[E0597]: `c1b` does not live long enough --> src/main.rs:48:23 | 37 | pub async fn with<'a, T, F, Fut>(&'a mut self, client: ClientId, func: F) -> Result | -- lifetime `'a` defined here ... 48 | Ok(func(&mut *c1b, &mut *c2b).await) | -----------^^^------------ | | | | | borrowed value does not live long enough | argument requires that `c1b` is borrowed for `'a` 49 | } | - `c1b` dropped here while still borrowed error[E0597]: `c2b` does not live long enough --> src/main.rs:48:34 | 37 | pub async fn with<'a, T, F, Fut>(&'a mut self, client: ClientId, func: F) -> Result | -- lifetime `'a` defined here ... 48 | Ok(func(&mut *c1b, &mut *c2b).await) | ----------------------^^^- | | | | | borrowed value does not live long enough | argument requires that `c2b` is borrowed for `'a` 49 | } | - `c2b` dropped here while still borrowed
I'm starting to run out of ideas at this point.a
Mutex
would face similar problems.I suppose I could solve this problem by passing in the `RefCell` itself to the function but I would prefer to hide the details of the inner workings of with
.use std::collections::HashMap; use std::cell::RefCell; use std::future::Future; #[derive(Copy, Clone, Hash, Eq, PartialEq)] struct ClientId(u32); struct ClientState { other_client: ClientId, some_data: u32, } #[derive(Debug)] enum StateError { Blah, } #[derive(Default)] struct State { state: HashMap<ClientId, RefCell<ClientState>>, } impl State { pub fn new_client(&mut self, client1: ClientId, client2: ClientId) { self.state.insert(client1, RefCell::new(ClientState { other_client: client2, some_data: 0, })); self.state.insert(client2, RefCell::new(ClientState { other_client: client1, some_data: 0, })); } pub async fn with<'a, T, F, Fut>(&'a mut self, client: ClientId, func: F) -> Result<T, StateError> where F: Fn(&'a RefCell<ClientState>, &'a RefCell<ClientState>) -> Fut, Fut: Future<Output=T>, { let c1 = self.state.get(&client).ok_or(StateError::Blah)?; let c2 = self.state.get(&c1.borrow().other_client).ok_or(StateError::Blah)?; Ok(func(c1, c2).await) } } #[async_std::main] async fn main() { let mut state = State::default(); state.new_client(ClientId(1), ClientId(2)); state.with(ClientId(1), |this, other| async { this.borrow_mut().some_data = 23; other.borrow_mut().some_data = 5; }).await.unwrap(); println!("c1 {}", state.state.get(&ClientId(1)).unwrap().borrow().some_data); println!("c2 {}", state.state.get(&ClientId(2)).unwrap().borrow().some_data); }
$ cargo run Compiling sufferingwithwith v0.1.0 (/home/jake/projects/sharnoth.com/0005) Finished dev [unoptimized + debuginfo] target(s) in 0.02s Running `target/debug/sufferingwithwith` c1 23 c2 5yeah I really do not like the `borrow_mut`s in the function itself BUT HEY IT WORKS.
in a questionable state of mind I figure "hey what happens if I send the `RefMut`s just directly by value?"
use std::collections::HashMap; use std::cell::{RefCell, RefMut}; use std::future::Future; use async_std::sync::{Arc, Mutex}; #[derive(Copy, Clone, Hash, Eq, PartialEq)] struct ClientId(u32); struct ClientState { other_client: ClientId, some_data: u32, } #[derive(Debug)] enum StateError { Blah, } #[derive(Default)] struct State { state: HashMap<ClientId, RefCell<ClientState>>, } impl State { pub fn new_client(&mut self, client1: ClientId, client2: ClientId) { self.state.insert(client1, RefCell::new(ClientState { other_client: client2, some_data: 0, })); self.state.insert(client2, RefCell::new(ClientState { other_client: client1, some_data: 0, })); } pub async fn with<'a, T, F, Fut>(&'a mut self, client: ClientId, func: F) -> Result<T, StateError> where F: Fn(RefMut<ClientState>, RefMut<ClientState>) -> Fut, Fut: Future<Output=T>, { let c1 = self.state.get(&client).ok_or(StateError::Blah)?; let c2 = self.state.get(&c1.borrow().other_client).ok_or(StateError::Blah)?; let c1b = c1.borrow_mut(); let c2b = c2.borrow_mut(); Ok(func(c1b, c2b).await) } } #[async_std::main] async fn main() { let mut state = State::default(); state.new_client(ClientId(1), ClientId(2)); state.with(ClientId(1), |mut this, mut other| async move { this.some_data = 23; other.some_data = 5; }).await.unwrap(); println!("c1 {}", state.state.get(&ClientId(1)).unwrap().borrow().some_data); println!("c2 {}", state.state.get(&ClientId(2)).unwrap().borrow().some_data); }
error: lifetime may not live long enough --> src/main.rs:57:51 | 57 | state.with(ClientId(1), |mut this, mut other| async move { | ______________________________--------___________-_^ | | | | | | | return type of closure `impl Futureoh nevermind me just being dumb over here ignoring that naturally those structures would have lifetimes built into them.` contains a lifetime `'2` | | has type `RefMut<'1, ClientState>` 58 | | this.some_data = 23; 59 | | other.some_data = 5; 60 | | }).await.unwrap(); | |_____^ returning this value requires that `'1` must outlive `'2` error: lifetime may not live long enough --> src/main.rs:57:51 | 57 | state.with(ClientId(1), |mut this, mut other| async move { | ________________________________________----------_^ | | | | | | | return type of closure `impl Future ` contains a lifetime `'4` | | has type `RefMut<'3, ClientState>` 58 | | this.some_data = 23; 59 | | other.some_data = 5; 60 | | }).await.unwrap(); | |_____^ returning this value requires that `'3` must outlive `'4`
anyway, it seems the core problems I am having is that references to outside data inside async closures is just not gonna work.so lets try some stupid shit and see which I hate least.
removing from hashmap to run the function then re-add after:
use std::collections::HashMap; use std::cell::{RefCell, RefMut}; use std::future::Future; use async_std::sync::{Arc, Mutex}; #[derive(Copy, Clone, Hash, Eq, PartialEq)] struct ClientId(u32); struct ClientState { other_client: ClientId, some_data: u32, } #[derive(Debug)] enum StateError { Blah, } #[derive(Default)] struct State { state: HashMap<ClientId, ClientState>, } impl State { pub fn new_client(&mut self, client1: ClientId, client2: ClientId) { self.state.insert(client1, ClientState { other_client: client2, some_data: 0, }); self.state.insert(client2, ClientState { other_client: client1, some_data: 0, }); } pub async fn with<'a, T, F, Fut>(&'a mut self, client: ClientId, func: F) -> Result<T, StateError> where F: Fn(&'a mut ClientState, &'a mut ClientState) -> Fut, Fut: Future<Output=T>, { let mut c1 = self.state.remove(&client).ok_or(StateError::Blah)?; let mut c2 = self.state.remove(&c1.other_client).ok_or(StateError::Blah)?; let result = func(&mut c1, &mut c2).await; let c1_client_id = c2.other_client; let c2_client_id = c1.other_client; self.state.insert(c1_client_id, c1); self.state.insert(c2_client_id, c2); Ok(result) } } #[async_std::main] async fn main() { let mut state = State::default(); state.new_client(ClientId(1), ClientId(2)); state.with(ClientId(1), |this, other| async move { this.some_data = 23; other.some_data = 5; }).await.unwrap(); println!("c1 {}", state.state.get(&ClientId(1)).unwrap().some_data); println!("c2 {}", state.state.get(&ClientId(2)).unwrap().some_data); }
error[E0597]: `c1` does not live long enough --> src/main.rs:45:27 | 37 | pub async fn with<'a, T, F, Fut>(&'a mut self, client: ClientId, func: F) -> Resulthahahah man jokes on me I have no idea what I am doing anymore.| -- lifetime `'a` defined here ... 45 | let result = func(&mut c1, &mut c2).await; | -----^^^^^^^---------- | | | | | borrowed value does not live long enough | argument requires that `c1` is borrowed for `'a` ... 53 | } | - `c1` dropped here while still borrowed error[E0597]: `c2` does not live long enough --> src/main.rs:45:36 | 37 | pub async fn with<'a, T, F, Fut>(&'a mut self, client: ClientId, func: F) -> Result | -- lifetime `'a` defined here ... 45 | let result = func(&mut c1, &mut c2).await; | --------------^^^^^^^- | | | | | borrowed value does not live long enough | argument requires that `c2` is borrowed for `'a` ... 53 | } | - `c2` dropped here while still borrowed error[E0503]: cannot use `c2.other_client` because it was mutably borrowed --> src/main.rs:47:28 | 37 | pub async fn with<'a, T, F, Fut>(&'a mut self, client: ClientId, func: F) -> Result | -- lifetime `'a` defined here ... 45 | let result = func(&mut c1, &mut c2).await; | ---------------------- | | | | | borrow of `c2` occurs here | argument requires that `c2` is borrowed for `'a` 46 | 47 | let c1_client_id = c2.other_client; | ^^^^^^^^^^^^^^^ use of borrowed `c2` error[E0503]: cannot use `c1.other_client` because it was mutably borrowed --> src/main.rs:48:28 | 37 | pub async fn with<'a, T, F, Fut>(&'a mut self, client: ClientId, func: F) -> Result | -- lifetime `'a` defined here ... 45 | let result = func(&mut c1, &mut c2).await; | ---------------------- | | | | | borrow of `c1` occurs here | argument requires that `c1` is borrowed for `'a` ... 48 | let c2_client_id = c1.other_client; | ^^^^^^^^^^^^^^^ use of borrowed `c1` error[E0505]: cannot move out of `c1` because it is borrowed --> src/main.rs:49:41 | 37 | pub async fn with<'a, T, F, Fut>(&'a mut self, client: ClientId, func: F) -> Result | -- lifetime `'a` defined here ... 45 | let result = func(&mut c1, &mut c2).await; | ---------------------- | | | | | borrow of `c1` occurs here | argument requires that `c1` is borrowed for `'a` ... 49 | self.state.insert(c1_client_id, c1); | ^^ move out of `c1` occurs here error[E0505]: cannot move out of `c2` because it is borrowed --> src/main.rs:50:41 | 37 | pub async fn with<'a, T, F, Fut>(&'a mut self, client: ClientId, func: F) -> Result | -- lifetime `'a` defined here ... 45 | let result = func(&mut c1, &mut c2).await; | ---------------------- | | | | | borrow of `c2` occurs here | argument requires that `c2` is borrowed for `'a` ... 50 | self.state.insert(c2_client_id, c2); | ^^ move out of `c2` occurs here
ok so references here are infinite forever suffering how about I just pass the state by value and try and make that work in some capacity. so to pass by value then I need to like return the data back out:
use std::collections::HashMap; use std::future::Future; #[derive(Copy, Clone, Hash, Eq, PartialEq)] struct ClientId(u32); #[derive(Clone)] struct ClientState { other_client: ClientId, some_data: u32, } #[derive(Debug)] enum StateError { Blah, } #[derive(Default)] struct State { state: HashMap<ClientId, ClientState>, } impl State { pub fn new_client(&mut self, client1: ClientId, client2: ClientId) { self.state.insert(client1, ClientState { other_client: client2, some_data: 0, }); self.state.insert(client2, ClientState { other_client: client1, some_data: 0, }); } pub async fn with<T, F, Fut>(&mut self, client: ClientId, func: F) -> Result<T, StateError> where F: Fn(ClientState, ClientState) -> Fut, Fut: Future<Output=(ClientState, ClientState, T)>, { let c1 = self.state.get(&client).ok_or(StateError::Blah)?; let c2 = self.state.get(&c1.other_client).ok_or(StateError::Blah)?; let (c1, c2, result) = func(c1.clone(), c2.clone()).await; let c1_client_id = c2.other_client; let c2_client_id = c1.other_client; self.state.insert(c1_client_id, c1); self.state.insert(c2_client_id, c2); Ok(result) } } #[async_std::main] async fn main() { let mut state = State::default(); state.new_client(ClientId(1), ClientId(2)); state.with(ClientId(1), |mut this, mut other| async move { this.some_data = 23; other.some_data = 5; (this, other, ()) }).await.unwrap(); println!("c1 {}", state.state.get(&ClientId(1)).unwrap().some_data); println!("c2 {}", state.state.get(&ClientId(2)).unwrap().some_data); }
$ cargo run Finished dev [unoptimized + debuginfo] target(s) in 0.02s Running `target/debug/sufferingwithwith` c1 23 c2 5I'm not sure if I dislike this more or less than the explicit
borrow_mut()
/lock()
in the closure.I do think this version is susceptible to weird timing issues like the other one was.so after some googling I find a possibly promising `FutureBoxLocal` future where I can specify a lifetime lets see how that goes
use std::collections::HashMap; use std::cell::RefCell; use futures::future::{Future, FutureExt, LocalBoxFuture}; #[derive(Copy, Clone, Hash, Eq, PartialEq)] struct ClientId(u32); struct ClientState { other_client: ClientId, some_data: u32, } #[derive(Debug)] enum StateError { Blah, } #[derive(Default)] struct State { state: HashMap<ClientId, RefCell<ClientState>>, } impl State { pub fn new_client(&mut self, client1: ClientId, client2: ClientId) { self.state.insert(client1, RefCell::new(ClientState { other_client: client2, some_data: 0, })); self.state.insert(client2, RefCell::new(ClientState { other_client: client1, some_data: 0, })); } pub fn with<'a, 'b, T, F, Fut>(&'a mut self, client: ClientId, func: F) -> Result<LocalBoxFuture<'a, T>, StateError> where 'a: 'b, F: 'a + FnOnce(&'b mut ClientState, &'b mut ClientState) -> Fut, Fut: Future<Output=T> + 'b { let mut c1 = self.state.get(&client).ok_or(StateError::Blah)?.borrow_mut(); let mut c2 = self.state.get(&c1.other_client).ok_or(StateError::Blah)?.borrow_mut(); Ok(async move { func(&mut *c1, &mut *c2).await } .boxed_local()) } } #[async_std::main] async fn main() { let mut state = State::default(); state.new_client(ClientId(1), ClientId(2)); state.with(ClientId(1), |this, other| async { this.some_data = 23; other.some_data = 5; }).unwrap().await; println!("c1 {}", state.state.get(&ClientId(1)).unwrap().borrow().some_data); println!("c2 {}", state.state.get(&ClientId(2)).unwrap().borrow().some_data); }
error[E0597]: `c1` does not live long enough --> src/main.rs:46:24 | 36 | pub fn with<'a, 'b, T, F, Fut>(&'a mut self, client: ClientId, func: F) -> Resultnope that sure didn't help!, StateError> | -- lifetime `'b` defined here ... 46 | func(&mut *c1, &mut *c2).await | -----------^^----------- | | | | | borrowed value does not live long enough | argument requires that `c1` is borrowed for `'b` 47 | } | - `c1` dropped here while still borrowed error[E0597]: `c2` does not live long enough --> src/main.rs:46:34 | 36 | pub fn with<'a, 'b, T, F, Fut>(&'a mut self, client: ClientId, func: F) -> Result , StateError> | -- lifetime `'b` defined here ... 46 | func(&mut *c1, &mut *c2).await | ---------------------^^- | | | | | borrowed value does not live long enough | argument requires that `c2` is borrowed for `'b` 47 | } | - `c2` dropped here while still borrowed
you know just for completions sake lets just do the
MutexGuard
version of that RefCell
implementation above oh wait look at those docs theres a lifetime parameter for MutexGuard<'a, T>
(and apparently for RefCell
which I totally did not notice which may have made my life easier a while ago). maybe that will save the dayuse std::collections::HashMap; use async_std::sync::{Mutex, MutexGuard}; use futures::future::Future; #[derive(Copy, Clone, Hash, Eq, PartialEq)] struct ClientId(u32); struct ClientState { other_client: ClientId, some_data: u32, } #[derive(Debug)] enum StateError { Blah, } #[derive(Default)] struct State { state: HashMap<ClientId, Mutex<ClientState>>, } impl State { pub fn new_client(&mut self, client1: ClientId, client2: ClientId) { self.state.insert(client1, Mutex::new(ClientState { other_client: client2, some_data: 0, })); self.state.insert(client2, Mutex::new(ClientState { other_client: client1, some_data: 0, })); } pub async fn with<'a, T, F, Fut>(&'a mut self, client: ClientId, func: F) -> Result<T, StateError> where F: FnOnce(MutexGuard<'a, ClientState>, MutexGuard<'a, ClientState>) -> Fut + 'a, Fut: Future<Output=T>, { let c1 = self.state.get(&client).ok_or(StateError::Blah)?.lock().await; let c2 = self.state.get(&c1.other_client).ok_or(StateError::Blah)?.lock().await; Ok(func(c1, c2).await) } } #[async_std::main] async fn main() { let mut state = State::default(); state.new_client(ClientId(1), ClientId(2)); state.with(ClientId(1), |mut this, mut other| Box::pin(async move { this.some_data = 23; other.some_data = 5; })).await.unwrap(); println!("c1 {}", state.state.get(&ClientId(1)).unwrap().lock().await.some_data); println!("c2 {}", state.state.get(&ClientId(2)).unwrap().lock().await.some_data); }
$ cargo run Finished dev [unoptimized + debuginfo] target(s) in 0.02s Running `target/debug/sufferingwithwith` c1 23 c2 5oh. huh. cool.