How to perform actions on a variable in a foreach while being able to use it afterwards

Viewed 29

I have the following example code (including use statements too so there's some context on types):

use actix_web::{HttpRequest, HttpResponse, Responder};
use awc::ClientRequest;

pub async fn proxy(req: HttpRequest) -> impl Responder {
    let response = construct_request(req).send().await;

    let body = response.unwrap().body().await.expect("");

    HttpResponse::Ok().body(body)
}

fn construct_request(req: HttpRequest) -> ClientRequest {
    let client = awc::Client::default();

    let mut new_req = client.get("url");

    req.headers().iter().for_each(|headerPair| {
        new_req.append_header((headerPair.0, headerPair.1));
    });
        
    new_req.content_type("application/json")
}
    

Currently, the compiler complains that I'm using the new_req value after it being moved in the foreach loop to copy the headers over, which is fair enough, that is what's happening.

error[E0507]: cannot move out of `new_req`, a captured variable in an `FnMut` closure
   --> src/routes/proxy.rs:18:9
    |
15  |       let mut new_req = client.get("http://localhost:8001/ping");
    |           ----------- captured outer variable
16  |
17  |       req.headers().iter().for_each(|headerPair| {
    |  ___________________________________-
18  | |         new_req.append_header((headerPair.0, headerPair.1));
    | |         ^^^^^^^ ------------------------------------------- `new_req` moved due to this method call
    | |         |
    | |         move occurs because `new_req` has type `ClientRequest`, which does not implement the `Copy` trait
19  | |     });
    | |_____- captured by this `FnMut` closure
    |
note: this function takes ownership of the receiver `self`, which moves `new_req`
   --> /Users/{sanitized}/awc-3.0.1/src/request.rs:186:30
    |
186 |     pub fn append_header(mut self, header: impl TryIntoHeaderPair) -> Self {
    |                              ^^^^

warning: variable does not need to be mutable
  --> src/routes/proxy.rs:15:9
   |
15 |     let mut new_req = client.get("http://localhost:8001/ping");
   |         ----^^^^^^^
   |         |
   |         help: remove this `mut`
   |
   = note: `#[warn(unused_mut)]` on by default

error[E0382]: use of moved value: `new_req`
  --> src/routes/proxy.rs:21:5
   |
15 |     let mut new_req = client.get("http://localhost:8001/ping");
   |         ----------- move occurs because `new_req` has type `ClientRequest`, which does not implement the `Copy` trait
16 |
17 |     req.headers().iter().for_each(|headerPair| {
   |                                   ------------ value moved into closure here
18 |         new_req.append_header((headerPair.0, headerPair.1));
   |         ------- variable moved due to use in closure
...
21 |     new_req.append_header(("custom", req.headers().get("custom").unwrap()))
   |     ^^^^^^^ value used here after move

What I can't find out is how I can work around this so that I can continue to use new_req after the foreach loop - is this possible? I've tried doing some googling around preventing this movement but I haven't managed to find anything (I'm assuming this is a very simple resolution that I just don't have the right words to discover)

1 Answers

You'll find by looking at the signature of .append_header() and other methods that they will consume self and return Self. This is a type of builder-pattern that is designed to work like this:

let new_req = client.get("url")
    .append_header(...)
    .append_header(...)
    .append_header(...)
    .content_type("application/json");

or like this:

let mut new_req = client.get("url");
new_req = new_req.append_header(...);
new_req = new_req.append_header(...);
new_req = new_req.append_header(...);
new_req = new_req.content_type("application/json");

Unfortunately, constant ownership transfers like this don't play well with closures if you want to keep the value afterwards. You're probably better suited just using a for-loop instead:

for header_pair in req.headers() {
    new_req = new_req.append_header(header_pair);
}

Or if you insist on using .for_each(), you can modify the request in-place by using .headers_mut():

req.headers().iter().for_each(|header_pair| {
    new_req.headers_mut().insert(header_pair.0.clone(), header_pair.1.clone());
});

If you're in the uncomfortable situation where you must take and reassign ownership and it must be while captured in a closure, you'll have to employ a trick using Option:

// put it in an option
let mut new_req_opt = Some(new_req);

req.headers().iter().for_each(|header_pair| {
    // take it out of the option
    let mut new_req = new_req_opt.take().unwrap();

    // do your thing
    new_req = new_req.append_header(header_pair);

    // put it back into the option
    new_req_opt = Some(new_req);
});

// take it out again at the end
let new_req = new_req_opt.unwrap();
Related