Hello I'm using actix-web to create a minimalist rest web app that gets data from sqlite database. I would like to share the closure that creates connection to the database to each handler via application state struct. I know I can solve my problem by sharing db_filepath and calling get_db_connection in the handlers. But my approach is more elegant and I'm interested in why my approach does not work.
Here is a minimal example with errors:
fn get_db_connection(db_filepath: &String) -> rusqlite::Connection {
let conn = rusqlite::Connection::open(db_filepath).unwrap();
conn
}
struct AppState {
get_db: Box<dyn FnMut() -> rusqlite::Connection>,
}
fn main() {
let db_filepath = "some/path.db".to_string() // is read from config file otherwise
let f = Box::new(move || { get_db_connection(&db_filepath) }); // f is captured outer variable
actix_web::server::new(move || {
actix_web::App::with_state(AppState { get_db: f }) // cannot move out of captured variable in an `Fn` closure
.scope("call-records", |call_record| {
call_record
.resource("/", |r| {
r.get().with(list_call_records);
})
})
}).bind("127.0.0.1:8088")
.unwrap()
.run();
}
fn list_call_records(req: HttpRequest<AppState>) -> Result<Json<Vec<CallRecord>>> {
let conn = (&req.state().get_db)();
// ...
// ...
Ok(Json(records))
}
Note that you have two layers of move || closures.
actix_web::server::new wants to call its closure any number of times, each time creating a new inner closure that owns its data, so it needs ability to have infinite number of copies of f.
But there's only one f, with only one copy of db_filepath, where you need infinite number of copies of db_filepath.
You can move f or db_filepath first to the closure of actix_web::server::new, and then clone it every time you create a new app.
Generally with Actix you'll need to wrap lots of things in Arc, because each thread gets its own server with its own copy of the data. Wrapping entire AppState in Arc is also usually a good idea (assuming you want it shared and the same for all threads).
fn main() {
let db_filepath = "some/path.db".to_string() // is read from config file otherwise
actix_web::server::new(move || {
let s = db_filepath.clone(); // this looks weird but Box::new(move || { get_db_connection(&db_filepath.clone()) }) does not work
let f = Box::new(move || { get_db_connection(&s) });
actix_web::App::with_state(AppState { get_db: f })
// ...
}
fn list_call_records(req: HttpRequest<AppState>) -> Result<Json<Vec<CallRecord>>> {
let conn = (&req.state().get_db)(); // cannot borrow data in a `&` reference as mutable
// ...
// ...
Ok(Json(records))
}
Now I have problem in the handler but I'm not doing anything with mutable?
In GC languages there's no difference between expression assigned to a variable or not, and variables are just a cosmetic help. In Rust variables have semantic meaning - they extend lifetimes.
So Box::new(move || { get_db_connection(db_filepath.clone()) }); still needs a copy of db_filepath to live in each closure to be cloned from.
OTOH let s = db_filepath.clone() means there's 1 copy of db_filepath outside, and each inner closure has 1 copy of s for itself (and doesn't touch db_filepath any more).