So I have a fair bit of rust experience and I am not stranger to dealing with lifetime issues, but for the first time in a while, I have a found a problem neither I or ChatGPT seems to be able to understand. And I think I may have found a weird inconsistancy in the borrow checker, but I figure I should come and ask here to see if anyone else know what's going on here before I make a bug report.
This code compiles:
fn update_reads(&mut self) {
let io = IoTaskPool::get();
let errors = io.scope(|s| {
for entry in self.streams.iter_mut() {
s.spawn(async {
match entry.1.update_read_buffer().await {
Err(e) => Some((*entry.0, e)),
_ => None,
}
})
}
});
self.handle_io_errors(errors);
}
but I am doing a refactor since I am using the pattern of spawning scoped futures a lot and have this as a result (also on a refactored struct btw):
fn for_each_async<F, T, Fut>(&mut self, function: F, io: &TaskPool) -> Vec
where F: Fn(&u64, &mut V) -> Fut,
Fut: Future<Output = T>, T: Send + 'static {
io.scope(|s| {
for (key, value) in self.map.iter_mut() {
s.spawn(function(key, value))
}
})
}
This is ment to make it easy to perform async operations on mass, such as read or writing from internal buffers to os sockets. But I am new to async code and I am awhere it can break in ways sync code can't so I desided to test it like this:
#[test]
fn test_for_each_async() {
let pool = TaskPool::new();
let mut map = SocketMapAsync::new();
let k1 = map.insert(50);
let k2 = map.insert(100);
let k3 = map.insert(150);
map.for_each_async(|key, value| async {
*value = *value / 2;
}, &pool);
assert!(*map.get(&k1).unwrap() == 25 && *map.get(&k2).unwrap() == 50 && *map.get(&k3).unwrap() == 150)
}
But this doesn't compile, the error I get is:
error: lifetime may not live long enough
--> crates\bevy_net\src\easy_sockets\mod.rs:69:37
|
69 | map.for_each_async(|key, value| async {
| ________________________------^
| | | |
| | | return type of closure {async block@crates\bevy_net\src\easy_sockets\mod.rs:69:37: 71:6}
contains a lifetime '2
| | has type &'1 mut i32
70 | | *value = *value / 2;
71 | | }, &pool);
| |^ returning this value requires that '1
must outlive '2
(Sorry the compiler error came out a bit wonky but it's just saying that the futrue returned by the async block outlives the lifetime of value, and is therefore illegal)
Even though the other example does. And the thing is, I think I can prove the borrow check wrong only using life times, and it goes like this:
-
self outlives scope (i.e io.scope)
-
scope outlives the tasks spawned in it using s.spawn() (this is enforced by explicit lifetimes on the scope and spawn methods)
*the lifetime of value is tied to the lifetime of self since self owns value
*so therefore: self: value: scope: spawned futures
And so everybody should be happy right? I assume thats why the genric code compiles, but for some reason when acctually used in the test it sudderley doesn't work.
I would greatley appreciate any help, thanks.