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++?

1
  • 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<Headers> 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 doc.rust-lang.org/stable/book/ch17-03-oo-design-patterns.html and doc.rust-lang.org/stable/book/… Commented Jul 13, 2019 at 19:48

3 Answers 3

48

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.

Sign up to request clarification or add additional context in comments.

6 Comments

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.
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.
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.
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.
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).
|
2

As of Rust 1.40.0 (released in December 2019), std::mem::take should be used. Rust docs mention:

pub fn take<T>(dest: &mut T) -> T
where
    T: Default,


Replaces dest with the default value of T, returning the previous dest value.

This is how it will be used in your example:

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(std::mem::take(&mut response.headers)),
    }
}

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

Comments

1

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)
}

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.