Basic reqwest usage

I thought I had learned enough about Rust now that I could try using the reqwest crate to send an HTTP request and get the response. I was clearly wrong. After about a half hour of trying every example I can find on the web, I'm ready to give up. Is this close?

extern crate reqwest;

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let mut res = reqwest::get("https://httpbin.org/headers")?;

    // copy the response body directly to stdout
    std::io::copy(&mut res, &mut std::io::stdout())?;

    Ok(())
}

(Playground)

Errors:

   Compiling playground v0.0.1 (/playground)
error[E0277]: the `?` operator can only be applied to values that implement `Try`
 --> src/main.rs:4:19
  |
4 |     let mut res = reqwest::get("https://httpbin.org/headers")?;
  |                   ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the `?` operator cannot be applied to type `impl Future`
  |
  = help: the trait `Try` is not implemented for `impl Future`
  = note: required by `into_result`

error: aborting due to previous error

For more information about this error, try `rustc --explain E0277`.
error: could not compile `playground`

To learn more, run the command again with --verbose.

Hi! I'm on mobile so I can't format code nicely in a reasonable amount of time, but I've fixed your example and added comments explaining why it didn't work: Playground.

Note that it still doesn't compile because the playground isn't set up to do networking (logically :wink:).

2 Likes

Here's another working version for additional reference: Rust Playground

2 Likes

The easiest fix, if you don’t want to go into async code yet, is to use reqwest::blocking::get instead of reqwest::get. With that change your code should compile.

4 Likes

For future reference by others, here's a version that parses the response body as JSON and deserializes it to a Rust struct (the commented out lines) or a vector of them (the uncommented lines).

extern crate reqwest;
extern crate tokio;

use serde::{Deserialize, Serialize};
use serde_json;

#[derive(Deserialize, Serialize, Debug)]
#[serde(rename_all = "camelCase")]
struct Todo {
    user_id: i32,
    id: i32,
    title: String,
    completed: bool,
}

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    //let url = "https://jsonplaceholder.typicode.com/todos/3";
    let url = "https://jsonplaceholder.typicode.com/todos";

    let res = reqwest::get(url).await?;
    let json = res.text().await.unwrap();
    println!("json = {}", json);

    //let todo: Todo = serde_json::from_str(&json).unwrap();
    let todos: Vec<Todo> = serde_json::from_str(&json).unwrap();

    //println!("todo = {:?}", todo);
    println!("todos = {:#?}", todos);

    Ok(())
}

Why is the error type Box<dyn std::error::Error> instead of just std::error::Error?

Because you can't directly return the unsized type dyn std::error::Error, you have to box it. Rust needs to know the size of types it returns from functions at compile time, and dyn std::error::Error could represent any type that implements the associated trait, so you need to box it to give it a fixed size (by putting the contents on the heap, and returning a pointer, i.e. boxing it)

std::error::Error is a trait, not a type. Many types are errors, but there is no single Error type. dyn Error is a feature of Rust called trait objects that allows you to store any value that implements a trait, so in this case we return any type that implements the Error trait. It has to be a Box because of the reasons Yato explained.

2 Likes

To add on to what @Kestrer said a little, some syntactical history can add to the confusion: A dyn Trait can also be written out as just Trait when it appears in a location that expects a type, such as a return type specifier. Not using dyn for a trait object is deprecated and produces a warning in the 2018 edition.

More information.

2 Likes