Weekly Rust Trivia is a problem-oriented series of articles that assist developers while learning Rust. Every article solves simple, everyday development tasks using the Rust standard library or leveraging popular and proven crates.

Question: How to download an image from the web and store it in a file using Rust?

The reqwest crate allows us to create HTTP requests with ease, we can combine functionality provided by the crate with the fs and io modules from Rusts standard library to solve this trivia. We also use the anyhow crate, to streamline error handling.

Today, we will create two implementations! The first implementation uses the blocking client API provided by reqwest. The second implementation will be non-blocking by leveraging the tokio crate as async runtime.

Blocking implementation

First let’s add the following dependencies using cargo add:

# Add anyhow as a dependency
cargo add anyhow
# Add reqwest with blocking feature
cargo add reqwest -F blocking

Having the crates added to our project, we can move on and implement downloading and storing the image from an URL:

use std::{fs::File, io::copy};
use anyhow::Result;

fn download_image(url: &str, file_name: &str) -> Result<()> {
    // Send an HTTP GET request to the URL
    let mut response = reqwest::blocking::get(url)?;

    // Create a new file to write the downloaded image to
    let mut file = File::create(file_name)?;

    // Copy the contents of the response to the file
    copy(&mut response, &mut file)?;

    Ok(())
}

In this example, we pass the URL of the image (url) and the desired file name (file_name) as arguments to the download_image function, which returns a anyhow::Result<()>.

First, we use reqwest::blocking::get to issue the HTTP GET request for downloading the image from the given url, which returns a reqwest::Response once the download has finished. By adding the ? (question mark operator), we immediately return any potential error that happens during the request. Next, we use File::create to create the file on the local machine. Again ? is used to return immediately in the case of any error. Finally, we use the copy function from the io module to copy the entire contents of the response body into the newly created file.

We can use the download_image function as shown here:

fn main() {
    let image_url = "https://www.rust-lang.org/static/images/rust-logo-blk.svg";
    let file_name = "rust-logo-blk.svg";
    match download_image_to(image_url, file_name) {
        Ok(_) => println!("image saved successfully"),
        Err(e) => println!("error while downloading image: {}", e),
    }
}

Non-blocking (async) implementation

In contrast to the first iteration, we can use tokio and leverage non-blocking APIs provided by reqwest to solve the trivia by applying asynchronous programming techniques. First, let’s add the necessary dependencies:

# Add anyhow as a dependency
cargo add anyhow
# Add reqwest with blocking feature
cargo add reqwest -F blocking
# Add tokio with full featureset
cargo add tokio -F full

Having the crates added to our project, we can move on and implement downloading and storing the image from an URL:

use std::{fs::File, io::{copy, Cursor}};
use anyhow::Result;

async fn download_image_to(url: &str, file_name: &str) -> Result<()> {
    // Send an HTTP GET request to the URL
    let response = reqwest::get(url).await?;
    // Create a new file to write the downloaded image to
    let mut file = File::create(file_name)?;
    
    // Create a cursor that wraps the response body
    let mut content =  Cursor::new(response.bytes().await?);
    // Copy the content from the cursor to the file
    copy(&mut content, &mut file)?;

    Ok(())
}

In contrast to the previous implementation, we mark our download_image_to function as async. Instead of leveraging the blocking API of reqwest, we call reqwest::get("").await? and wait for the server to return a Response. Next, we use std::io::Cursor to create an in-memory reader for the received response, which we’ll then pass to the copy function.

We can use the async download_image function as shown here:

#[tokio::main]
async fn main() -> Result<()> {
    let image_url = "https://www.rust-lang.org/static/images/rust-logo-blk.svg";
    let file_name = "rust-logo-blk.svg";
    match download_image_to(image_url, file_name).await {
        Ok(_) => println!("image saved successfully"),
        Err(e) => println!("error while downloading image: {}", e),
    }
    Ok(())
}