Actix-web thread borrow and move problem

Hi everyone!, I'm learning Rust by making a small project, a backend RESTful web service using Actix-web framework and mysql crate.
In Actix I create a struct AppData that contains application data and a Pool of mysql.
I need a handler to do some db AND IO operations, so in it I use web::block for this operations. The problem is that I need to access values from AppData in every closure of web::block, but when I call the second web::block closure 'AppData' has moved to the first web::block scope. What is the best aproach to handle this? I tried using Arc but I am not sure if I do it in a correct way. A sample code is:

async fn greet(appdata: web::Data<AppData>) -> impl Responder {
    
    //Web block to offload database operations 
    let sqlresult = web::block(move|| { 
        let row: Result<Option<String>> = appdata.pool.get_conn().unwrap().query_first("SELECT '2';"); row
    }).await.unwrap();
 
    //Web block to offload io operations 
    let fileresult = web::block(move|| { 
        let mut file = File::create(format!("{}/foo.txt",appdata.tempfolder)).unwrap();
        file.write_all(sqlresult.unwrap().as_bytes()).map(|_| file)
    }).await.unwrap();
    
    //Do something more with appdata.tempfolder value
    format!("{:?}",appdata.tempfolder)
}

#[actix_web::main]
async fn main() -> std::io::Result<()> {
    let mysqlurl = "mysql://root:root@localhost:23306/pacsdb";

    let appdata = web::Data::new(AppData{ 
            tempfolder: String::from("needed"),
            visit_count: Mutex::new(0),
            pool: Pool::new(Opts::from_url(mysqlurl).unwrap()).unwrap(),
    });

    HttpServer::new(move|| {
        App::new()
            .app_data(appdata.clone())
            .route("/", web::get().to(greet))
    })
    .bind(("127.0.0.1", 8080))?
    .run()
    .await
}

pub struct AppData {
    pub tempfolder: String,
    pub visit_count: Mutex<usize>,
    pub pool: Pool,
}

and the error is:

error[E0382]: use of moved value: `appdata`
  --> src/main.rs:16:33
   |
8  | async fn greet(appdata: web::Data<AppData>) -> impl Responder {
   |                ------- move occurs because `appdata` has type `actix_web::web::Data<AppData>`, which does not implement the `Copy` trait
...
11 |     let sqlresult = web::block(move|| { 
   |                                ------ value moved into closure here
12 |         let row: Result<Option<String>> = appdata.pool.get_conn().unwrap().query_first("SELECT '2';"); row
   |                                           ------- variable moved due to use in closure
...
16 |     let fileresult = web::block(move|| { 
   |                                 ^^^^^^ value used here after move
17 |         let mut file = File::create(format!("{}/foo.txt",appdata.tempfolder)).unwrap();
   |                                                          ------- use occurs due to use in closure

error[E0382]: borrow of moved value: `appdata`
  --> src/main.rs:22:20
   |
8  | async fn greet(appdata: web::Data<AppData>) -> impl Responder {
   |                ------- move occurs because `appdata` has type `actix_web::web::Data<AppData>`, which does not implement the `Copy` trait
...
16 |     let fileresult = web::block(move|| { 
   |                                 ------ value moved into closure here
17 |         let mut file = File::create(format!("{}/foo.txt",appdata.tempfolder)).unwrap();
   |                                                          ------- variable moved due to use in closure
...
22 |     format!("{:?}",appdata.tempfolder)
   |                    ^^^^^^^^^^^^^^^^^^ value borrowed here after move
   |
   = note: borrow occurs due to deref coercion to `Arc<AppData>`
note: deref defined here
  --> /Users/nico/.cargo/registry/src/github.com-1ecc6299db9ec823/actix-web-3.3.2/src/data.rs:85:5
   |
85 |     type Target = Arc<T>;
   |     ^^^^^^^^^^^^^^^^^^^^^

Thanks to everyone!!

You could decompose appdata e.g. via

let AppData { pool, tempfolder, ..} = appdata.into_inner();

Then pass pool to one closure, and tempfolder to the other.

If you also need access to tempfolder after the closure, you'll need to share it somehow, unfortunately. Depending on what type tempfolder is you could create a copy of it, or share it via Arc. The latter could look like

let AppData { pool, tempfolder, ..} = appdata.into_inner();
let tempfolder = Arc::new(tempfolder);

....

let fileresult = web::block({
    let tempfolder = tempfolder.clone();
    move || { 
        let mut file = File::create(format!("{}/foo.txt", tempfolder)).unwrap();
        file.write_all(sqlresult.unwrap().as_bytes()).map(|_| file)
    }
}).await.unwrap();

Thanks for the quick answer. But I can't decompose AppData with
let AppData { pool, tempfolder, ..} = appdata.into_inner();
because is web::Data Type. I get the error:

error[E0308]: mismatched types
  --> src/main.rs:11:9
   |
11 |     let AppData { pool, tempfolder, visit_count } = appdata.into_inner();
   |         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^   -------------------- this expression has type `Arc<AppData>`
   |         |
   |         expected struct `Arc`, found struct `AppData`
   |
   = note: expected struct `Arc<AppData>`
              found struct `AppData`

So I figured out than can be decomposed with
let AppData { pool, tempfolder, visit_count } = &*appdata.into_inner(); but really I don't understand what it's happening here...
Variables tempfolder, pool, visit_count are references to the attributes of AppData struct passed in the parameter function?
How can I interpret the &*appdata in a Arc??
With your help I can refactor de handler function to:

async fn greet(appdata: web::Data<AppData>) -> impl Responder {
    let AppData { pool, tempfolder, visit_count } = &*appdata.into_inner();
    
    //1. From web::Data<AppData> to Arc<AppData> type
    let appdata = appdata.into_inner(); 

    //2. Arc<String>
    let filepath = Arc::new(String::from("./tmp/file"));
    
    //Web block to offload database operations 
    let arc_appdata = Arc::clone(&appdata);
    let sqlresult = web::block(move|| { 
        let row: Result<Option<String>> = arc_appdata.pool.get_conn().unwrap().query_first("SELECT '2';"); row
    }).await.unwrap();

    //Web block to offload io operations
    let arc_appdata = Arc::clone(&appdata);
    let fpath = Arc::clone(&filepath); 
    let fileresult = web::block(move|| { 
        let mut file1 = File::create(&*fpath).unwrap();  // Why &*fpath ?
        let mut file2 = File::create(&arc_appdata.tempfolder).unwrap(); // WHY  &arc_appdata.tempfolder ?
        file1.write_all(sqlresult.unwrap().as_bytes()).map(|_| file1)
    }).await.unwrap();
    
    //Do something more with appdata.tempfolder value
    format!("{:?}",appdata.tempfolder)
}

But if you see to create a file I have to pass &*fpath and &arc_appdata.tempfolder, why?
Thanks and sorry for my ignorance

Oh! Sorry, I've overlooked the -> Arc<T> return type of web::Data::into_inner; I'm not familiar with actix web at all and just quickly looked it up and missed that detail. You can't destructure the Arc, so ignore my hint from before. However this makes things a lot simpler immediately. The data is already behind Arc, so you can easily share it by cloning the arc. Looks like you've found that out yourself now as-well. In-fact you could directly clone the web::Data<...> if you want with the same effect. Just do something like

    //Web block to offload database operations 
    let sqlresult = web::block({
        let appdata = appdata.clone();
        move || { 
            let row: Result<Option<String>> = appdata.pool.get_conn().unwrap().query_first("SELECT '2';"); row
        }
    }).await.unwrap();

on the first closure, and the same thing on the second and everything might work. Or if you prefer that syntactically:

    //Web block to offload database operations
    let appdata_clone = appdata.clone();
    let sqlresult = web::block(move|| { 
        let row: Result<Option<String>> = appdata_clone .pool.get_conn().unwrap().query_first("SELECT '2';"); row
    }).await.unwrap();

or something like that.

filepath does not seem to be shared in the first place, so it look like you don't need to put it in an Arc at all. Unless you are planning on using it in other places as-well.

Anyways, answering the question: the expression arc_appdata.tempfolder cannot be used by-value at all because you cannot move out of the AppData behind Arc. So creating a reference &arc_appdata.tempfolder instead is the most natural thing. About why fpath or &fpath won't work: That has to do with that types of arguments File::create supports.

pub fn create<P: AsRef<Path>>(path: P) -> Result<File>

looking at existing AsRef implementations, Arc<String> would only implement AsRef<String> and the same thing applies to &Arc<String>. Since fpath: Arc<String> and &fpath: &Arc<String>, neither of these will work; but &*fpath: &String, which implements AsRef<Path>, so that works. You could also use the String::as_str method and write fpath.as_str() if you prefer that; that creates an &str which implements AsRef<Path> as-well.

Ok, I have to read and test more about AsRef, but I understand you answer. Thank a lot!. You clarified several concepts for me.

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.