Search code examples
rustborrowing

Why does calling a method on a mutable variable cause its value to be moved?


I'm using http::Request::builder() to create a request and uri() to set the request uri. The following code compiles without issues:

fn get_oauth_token() {
    let mut request = Request::builder().uri(&*OAUTH_URL);
    let uri = match request.uri_ref() {
        Some(uri) => uri.to_string(),
        None => String::from(""),
    };
    println!("{}", uri)
}

However, if I declare the variable request first and call the uri() method on that variable, I get the error shown below:

fn get_oauth_token() {
    let mut request = Request::builder();
    request.uri(&*OAUTH_URL);
    let uri = match request.uri_ref() {
        Some(uri) => uri.to_string(),
        None => String::from(""),
    };
    println!("{}", uri)
}
error[E0382]: borrow of moved value: `request`
 --> src\main.rs:9:21
  |
7 |     let mut request = Request::builder();
  |         ----------- move occurs because `request` has type `http::request::Builder`, which does not implement the `Copy` trait
8 |     request.uri(&*OAUTH_URL);
  |     ------- value moved here
9 |     let uri = match request.uri_ref() {
  |                     ^^^^^^^ value borrowed here after move

Notice that the only difference between both snippets is that I split the line let mut request = Request::builder().uri(&*OAUTH_URL); into let mut request = Request::builder(); and request.uri(&*OAUTH_URL);.

The variable OAUTH_URL was declared like this (I'm using dotenv to load it from a .env file):

lazy_static! {
    static ref OAUTH_URL: String = env::var("OAUTH_URL").unwrap();
}

Now I've looked at the explanation for the error E0382 and it tells me that the value of a variable will be moved if you assign it to another variable which makes sense. However, I don't understand why that happens in this case because I only declared one variable and then called a method on that variable.

Why does the value of request get moved when I call request.uri(&*OAUTH_URL);?


Solution

  • According to the documentation, the method moves the object and then returns it back. So the two snippets are very different.

    The first snippet is the correct way as the value is moved through the chain and stored in request; perfectly acceptable! In the other snippet, request.uri(&*OAUTH_URL); moves the value into the method and then throws the object away, i.e. you've just consumed and discarded the object.

    For them to be equal, you'd have to write request = request.uri(&*OAUTH_URL);

    First snippet (correct):

    fn get_oauth_token() {
        // Object moves from Request::builder() -> .uri(&*OAUTH_URL) -> request
        let mut request = Request::builder().uri(&*OAUTH_URL);
        let uri = match request.uri_ref() {
            Some(uri) => uri.to_string(),
            None => String::from(""),
        };
        println!("{}", uri)
    }
    

    Second snippet (error):

    fn get_oauth_token() {
        // Object moves from Request::builder() -> request
        let mut request = Request::builder();
        // Object moves from request -> .uri(&*OAUTH_URL) -> nowhere
        request.uri(&*OAUTH_URL);
        let uri = match request.uri_ref() {
            Some(uri) => uri.to_string(),
            None => String::from(""),
        };
        println!("{}", uri)
    }
    

    Alternate snippet (correct):

    fn get_oauth_token() {
        // Object moves from Request::builder() -> request
        let mut request = Request::builder();
        // Object moves from request -> .uri(&*OAUTH_URL) -> request again
        request = request.uri(&*OAUTH_URL);
        let uri = match request.uri_ref() {
            Some(uri) => uri.to_string(),
            None => String::from(""),
        };
        println!("{}", uri)
    }