Reputation: 3644
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
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
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
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