I was trying to have a Hashmap that can be used in both app data and in the middleware, but I am getting lifetime errors.
I can do that with Atomics but there is no Atomic for hashmap, and a hashmap cannot be static, how do I make sure that it lives longer than the big chunky function below?
#[derive(Clone, Debug)]
struct HashMapContainer(pub Arc<Mutex<HashMap<String, u32>>>);
#[actix_web::main]
async fn main() -> io::Result<()> {
// Here's what I want to do, but with a hashmap instead
static COUNTER: AtomicU32 = AtomicU32::new(1);
let hashmap = HashMapContainer(Arc::new(Mutex::new(HashMap::new())));
HttpServer::new(move || {
App::new()
// Middleware function called every time when there is a request no matter what is the path
.wrap_fn(|req, srv| {
// increase counter by 1
COUNTER.store(COUNTER.load(Relaxed) 1, Relaxed);
// So I can do stuff with the hashmap (should be mutable)
dbg!(&hashmap.0.lock().unwrap());
srv.call(req)
})
// I also want to use those 2 variables as app data
.app_data(web::Data::new(SessionStats::new()))
.app_data(web::Data::new(hashmap.clone()))
})
.bind(("0.0.0.0", 8080))?
.run()
.await?;
Ok(())
}
Error:
error: lifetime may not live long enough
--> src/main.rs:17:9
|
16 | HttpServer::new(move || {
| ------- lifetime `'1` represents this closure's body
17 | / App::new()
19 | | .wrap_fn(|req, srv| {
... |
23 | | srv.call(req)
24 | | })
| |______________^ argument requires that `'1` must outlive `'static`
|
= note: closure implements `Fn`, so references to captured variables can't escape the closure
CodePudding user response:
Your approach of using an Arc<Mutex>
is 100% correct.
There are just some detail problems with your code.
All the problems you see come from ownership. The closures all have to be move
to own a Arc
object. But then they consume the outer one, so you have to clone it beforehand. That makes the code a little ugly, but well, that's how it is.
Here you go:
use std::{
collections::HashMap,
io,
sync::{
atomic::{AtomicU32, Ordering::Relaxed},
Arc, Mutex,
},
};
use actix_web::{dev::Service, web, App, HttpServer};
struct SessionStats;
impl SessionStats {
pub fn new() -> Self {
Self
}
}
#[derive(Clone, Debug)]
struct HashMapContainer(pub Arc<Mutex<HashMap<String, u32>>>);
#[actix_web::main]
async fn main() -> io::Result<()> {
// Here's what I want to do, but with a hashmap instead
static COUNTER: AtomicU32 = AtomicU32::new(1);
let hashmap = HashMapContainer(Arc::new(Mutex::new(HashMap::new())));
// Clone here, this one will be owned by the first closure
let hashmap_for_httpserver = hashmap.clone();
HttpServer::new(move || {
// Clone the hashmap owned by the closure.
// The one that the closure owns is only borrowable, but we want to have an owned one so we can
// move it into the next closure
let hashmap_for_app = hashmap_for_httpserver.clone();
App::new()
// Middleware function called every time when there is a request no matter what is the path
.wrap_fn(move |req, srv| {
// increase counter by 1
COUNTER.store(COUNTER.load(Relaxed) 1, Relaxed);
// So I can do stuff with the hashmap (should be mutable)
dbg!(&hashmap_for_app.0.lock().unwrap());
srv.call(req)
})
// I also want to use those 2 variables as app data
.app_data(web::Data::new(SessionStats::new()))
.app_data(web::Data::new(hashmap_for_httpserver.clone()))
})
.bind(("0.0.0.0", 8080))?
.run()
.await?;
Ok(())
}