Craig M. Brandenburg
Craig M. Brandenburg

Reputation: 3644

How to move one field out of a struct that implements Drop trait?

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

Upvotes: 36

Views: 16352

Answers (3)

Arpit Saxena
Arpit Saxena

Reputation: 57

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

Upvotes: 1

Mark Saving
Mark Saving

Reputation: 1797

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

Upvotes: 1

Francis Gagn&#233;
Francis Gagn&#233;

Reputation: 65822

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.

Upvotes: 47

Related Questions