36

Here's an invalid Rust program (Rust version 1.1) with a function that does an HTTP client request and returns only the headers, dropping all other fields in the response.

extern crate hyper;

fn just_the_headers() -> Result<hyper::header::Headers, hyper::error::Error> {
    let c = hyper::client::Client::new();
    let result = c.get("http://www.example.com").send();
    match result {
        Err(e) => Err(e),
        Ok(response) => Ok(response.headers),
    }
}

fn main() {
    println!("{:?}", just_the_headers());
}

Here are the compiler errors:

main.rs:8:28: 8:44 error: cannot move out of type `hyper::client::response::Response`, which defines the `Drop` trait
main.rs:8         Ok(response) => Ok(response.headers),
                                 ^~~~~~~~~~~~~~~~
error: aborting due to previous error

I understand why the borrow checker doesn't accept this program—i.e., that the drop function will use the response after it has had its headers member moved.

My question is: How can I get around this and still have good safe Rust code? I know I can do a copy, via clone(), like so:

Ok(response) => Ok(response.headers.clone()),

But, coming from C++, that seems inefficient. Why copy when a move should suffice? I envision in C++ doing something like the following to force a call to a move constructor, if available:

headers_to_return = std::move(response.headers);

Is there any way to forgo the copy in Rust and instead force a move, similar to C++?

Boiethios
  • 38,438
  • 19
  • 134
  • 183
Craig M. Brandenburg
  • 3,354
  • 5
  • 25
  • 37
  • This won't technically move the member value, but if you're OK to change your structure a bit, but you can wrap the headers by changing the type of `response.headers` to `Option` and `take()` its value. This will reset the value to None, which is useful if you're unable to find a good default value for your type (e.g. a `thread`). This is done in https://doc.rust-lang.org/stable/book/ch17-03-oo-design-patterns.html and https://doc.rust-lang.org/stable/book/ch20-03-graceful-shutdown-and-cleanup.html – hsandt Jul 13 '19 at 19:48

2 Answers2

45

You can use std::mem::replace() to swap the field with a new blank value in order to transfer ownership to you:

extern crate hyper;

fn just_the_headers() -> Result<hyper::header::Headers, hyper::error::Error> {
    let c = hyper::client::Client::new();
    let result = c.get("http://www.example.com").send();
    match result {
        Err(e) => Err(e),
        Ok(mut response) => Ok(std::mem::replace(&mut response.headers, hyper::header::Headers::new())),
    }
}

fn main() {
    println!("{:?}", just_the_headers());
}

Here, we're replacing response.headers with a new empty set of headers. replace() returns the value that was stored in the field before we replaced it.

Francis Gagné
  • 60,274
  • 7
  • 180
  • 155
  • 5
    It might be worth nothing that `std::mem::replace` in Rust is *more or less* what `std::move` is in C++. Because the source and destination must be valid to destruct both before *and after* a move, C++ doesn't really move, it swaps. – DK. Jul 09 '15 at 05:02
  • 3
    Indeed, with the difference that in C++, it is the class that decides how to implement the move (in the move constructor or move assignment operator), whereas `std::mem::replace` requires the caller to provide a suitable value. In fact, `std::mem::replace` is implemented in terms of [`std::mem::swap`](http://doc.rust-lang.org/stable/std/mem/fn.swap.html). – Francis Gagné Jul 09 '15 at 05:05
  • 4
    You might wish to note that Rust also places on emphasis on extremely efficient "default constructs", for example neither `String::new()` nor `Vec::new()` allocate memory, which is what makes this replace as efficient as the C++ move on top of being safer. – Matthieu M. Jul 09 '15 at 08:04
  • @FrancisGagné and to finish that thought, Rust types don't define how they swap either, their byte representations are simply swapped. – bluss Jul 09 '15 at 09:18
  • 1
    Thanks! Is using `std::mem::replace` for this use case idiomatic? (Is what I'm trying to do — to force a _move_ instead of a _copy_ — idiomatic?) I ask because the call to `std::mem::replace` seems like a lot of typing to do something that could be a common use case. – Craig M. Brandenburg Jul 09 '15 at 12:27
  • 2
    `std::mem::replace` seems to be the most suitable tool to use to take ownership of a value you can't take ownership of with Rust's standard ownership rules. Don't forget you can use `use` declarations to make names shorter, e.g. `use std::mem;`, then `mem::replace`, or `use std::mem::replace`, then `replace`. The preferred style is to use functions qualified on the module (`mem::replace`), but types unqualified (`Headers`). – Francis Gagné Jul 09 '15 at 23:42
0

It's worth noting that the hyper crate now has a different API which supports taking the headers by value. Here is the solution for hyper 14.2 (Rust edition 2021, version 1.69):

extern crate http;
extern crate hyper;

async fn just_the_headers() -> Result<http::header::HeaderMap, hyper::Error> {
    let c = hyper::client::Client::new();
    let result = c
        .get(hyper::Uri::from_static("http://www.example.com"))
        .await;
    match result {
        Err(e) => Err(e),
        Ok(response) => Ok(response.into_parts().0.headers),
    }
}

A lot has changed since Rust 1.1. async/await is now a first-class part of the language (stabilized in Rust 1.39). We also have the new ? operator, stabilized in Rust 1.13. Using this operator, the code becomes

extern crate http;
extern crate hyper;

async fn just_the_headers() -> Result<http::header::HeaderMap, hyper::Error> {
    Ok(hyper::client::Client::new()
        .get(hyper::Uri::from_static("http://www.example.com"))
        .await?
        .into_parts()
        .0
        .headers)
}
Mark Saving
  • 1,752
  • 7
  • 11