Downloading Files with Progress Bars in Rust
Rust
https://github.com/jonaspleyer/2025-09-02-download-file-progress-bar-rust
I could not find any short snippets on how to download files in Rust using a progress bar. So here is my take on it.
During the past days, I was working on a project in which I wanted to analyze specific crates of the
Rust ecosystem.
To do this I wanted to download the dump of [crates.io] which is described in the
Data Access Policy.
Since the async_std has been discontinued and recommends to
use smol, I did pick it up as well for my async runtime.
However, if you want to combine it with reqwest, there are
some challenges.
reqwest expects a running tokio adapter and will introduce runtime panics if it cannot find it.
Therefore, we have to also use the async_compat crate
which was specifically designed to circumvent these challenges.
Additionally, I want to provide a progress bar for which I will use
kdam.
Finally, to not having to worry about any errors, I use anyhow.
use anyhow::Result;
const DB_DUMP_LINK: &str = "https://static.crates.io/db-dump.tar.gz";
// This applies the `main` macro of the smol_macro crate.
#[macro_rules_attribute::apply(smol_macros::main)]
async fn main() -> Result<()> {
// Open a file to store the downloaded data.
let mut file = smol::fs::File::create("/temp/db-dump.tar.gz").await?;
// Create request and obtain response
// Note: We need to use async_compat for compatibility with tokio.
let request = async_compat::Compat::new(reqwest::get(DB_DUMP_LINK));
let mut response = request.await?;
// Obtain the number of total bytes in the response
let total_bytes = response.content_length().unwrap_or_default() as usize;
// Define the format of the progress bar
let bar_format = "{desc}{percentage:3.0}%|{animation}| {count}/{total} [{elapsed} {rate:.2}{unit}/s{postfix}]";
// Construct a new progress bar
let mut pb = kdam::BarBuilder::default()
.total(total_bytes)
.unit_scale(true)
.unit("Mb")
.bar_format(bar_format)
.build()
.map_err(|e| anyhow::anyhow!(e))?;
// Main loop to obtain chunks iteratively
while let Some(chunk) = response.chunk().await? {
// Write next chunk to file
use smol::io::AsyncWriteExt;
file.write_all(&chunk).await?;
// Update progress bar
use kdam::BarExt;
pb.update(chunk.len())?;
}
Ok(())
}
And voila! The resulting output looks quite nice.
# cargo run -r
Finished `release` profile [optimized] target(s) in 0.07s
Running `target/release/download_file`
45%|██████████████████▋ | 532M/1.18G [00:08 64Mb/s]
For completeness the Cargo.toml file which I have used.
[package]
name = "download_file"
version = "0.1.0"
edition = "2024"
[dependencies]
anyhow = "1.0.99"
async-compat = "0.2.5"
kdam = { version = "0.6.3", features = ["template"] }
macro_rules_attribute = "0.2.2"
reqwest = "0.12.23"
smol = "2.0.2"
smol-macros = "0.1.1"
Note
If you want to use this snippet to download files when they are not present, remember to not only
check that the file exists but also that it is complete.
You can achieve this by comparing the total_bytes variable with the actual file-size.
It would be even better to compare checksums of course.