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 Rust’s 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:

BASH
# 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 a URL:

RUST
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 and fail on non-success status codes
    let mut response = reqwest::blocking::get(url)?.error_for_status()?;

    // 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 an 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::blocking::Response once the server responds. By adding the ? (question mark operator), we immediately return any potential error that happens during the request. We also call error_for_status() to turn HTTP error codes such as 404 into errors. Otherwise, we would happily write the error page to our file. 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:

RUST
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(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:

BASH
# Add anyhow as a dependency
cargo add anyhow
# Add reqwest
cargo add reqwest
# 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 a URL:

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

async fn download_image(url: &str, file_name: &str) -> Result<()> {
    // Send an HTTP GET request to the URL and fail on non-success status codes
    let response = reqwest::get(url).await?.error_for_status()?;
    // 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 function as async. Instead of leveraging the blocking API of reqwest, we call reqwest::get(url).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 body, which we’ll then pass to the copy function.

We can use the async download_image function as shown here:

RUST
#[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(image_url, file_name).await {
        Ok(_) => println!("image saved successfully"),
        Err(e) => println!("error while downloading image: {e}"),
    }
    Ok(())
}

Looking for more Rust trivia around files and the web? Check out How to Compute a SHA256 Hash of a File , How to Retrieve Image Dimensions , or my deeper dive on calling HTTP APIs in Rust with reqwest .