Export files to Microsoft Azure in Rust
Upload a local file of up to 16 MiB to an existing private Azure Blob Storage container with this complete Rust program. It sends one conditional request and refuses to replace an existing blob. Empty files and binary data are valid inputs.
This is a maintenance example for the unofficial azure_storage_blobs 0.21.0 crate.
Microsoft’s legacy crate notice
says it has no plans to update that crate. For new applications, evaluate the
official azure_storage_blob SDK,
whose authentication examples use Microsoft Entra ID. Its API differs from this pinned example;
changing the dependency name alone is not a migration.
Prerequisites
- A Linux shell with Bash, Rust, Cargo, and the system linker needed by your Rust installation. This example was tested with Rust and Cargo 1.98.1, using the 2021 edition. That is a tested environment, not a compiler minimum. See the Rust installation guide for installing a current toolchain.
- A C compiler,
pkg-config, and OpenSSL libraries and headers. The default TLS transport needs these on Linux; the OpenSSL crate’s build guide lists the packages for common distributions. Install those prerequisites before creating the project. - An Azure Storage account that already allows Shared Key authentication, and its account name and access key. This path is for an existing Shared Key integration. Microsoft recommends Microsoft Entra ID for new applications; do not enable Shared Key just to follow this example.
- An existing private container, provisioned separately. The program neither creates containers nor changes their access settings. A private container requires authorization to read its blobs, as described in Azure’s anonymous-access documentation.
- A complete regular file that will remain unchanged during the upload. Command arguments, including the local path, must be valid UTF-8. Use this example sequentially.
Project setup
Run this from a writable directory. The block checks the build tools and creates a new directory; if
azure_export already exists, it stops without entering or changing that project.
(
command -v cargo >/dev/null &&
command -v rustc >/dev/null &&
command -v cc >/dev/null &&
command -v pkg-config >/dev/null &&
pkg-config --exists openssl &&
mkdir azure_export &&
mkdir azure_export/src
) && cd azure_export
Continue only after that succeeds. Save the following as Cargo.toml. The empty [workspace]
keeps this project separate from an enclosing Cargo workspace. The Tokio filesystem and I/O
features are explicit. Keep the generated Cargo.lock with your application to retain its
resolved transitive dependencies.
If directory creation fails partway through, inspect what was created before trying again. Use a fresh writable parent directory for a new attempt; the block deliberately preserves existing files.
[package]
name = "azure_export"
version = "0.1.0"
edition = "2021"
[workspace]
[dependencies]
azure_core = "=0.21.0"
azure_storage = "=0.21.0"
azure_storage_blobs = "=0.21.0"
tokio = { version = "=1.48.0", features = ["fs", "io-util", "macros", "rt-multi-thread", "time"] }
Azure client initialization
Supply AZURE_STORAGE_ACCOUNT and AZURE_STORAGE_KEY through your secret manager or process
environment. The key is a storage account access key, not a connection string. The program calls
StorageCredentials::access_key(); it does not load an Azure CLI login. Keep the key out of source
control and terminal command arguments.
Save the complete program below as src/main.rs.
File upload implementation
The program reads at most 16 MiB plus one byte before sending a request, and rejects an oversized
input. The 16 MiB ceiling is this example’s policy, not Azure’s storage limit. It buffers the input
for one put_block_blob operation; it does not split the file into staged blocks or upload in
parallel. The destination blob name is a separate argument from the local path.
use azure_core::{prelude::IfMatchCondition, RetryOptions};
use azure_storage::StorageCredentials;
use azure_storage_blobs::prelude::{BlobServiceClient, ClientBuilder};
use std::{env, error::Error, path::Path, time::Duration};
use tokio::{fs::File, io::AsyncReadExt, time::timeout};
type AppResult<T> = Result<T, Box<dyn Error + Send + Sync>>;
const MAX_BYTES: u64 = 16 * 1024 * 1024;
async fn upload_file(
service: &BlobServiceClient,
container: &str,
blob_name: &str,
path: &Path,
) -> AppResult<()> {
if container.is_empty() || blob_name.is_empty() {
return Err("Container and blob name must not be empty".into());
}
let file = File::open(path).await?;
if !file.metadata().await?.is_file() {
return Err("Input must be a regular file".into());
}
let mut contents = Vec::new();
file.take(MAX_BYTES + 1).read_to_end(&mut contents).await?;
if contents.len() as u64 > MAX_BYTES {
return Err("Input exceeds the 16 MiB example limit".into());
}
let blob = service.container_client(container).blob_client(blob_name);
blob.put_block_blob(contents)
.content_type("application/octet-stream")
.if_match(IfMatchCondition::NotMatch("*".to_owned()))
.await?;
Ok(())
}
async fn run() -> AppResult<()> {
let args: Vec<String> = env::args().skip(1).collect();
if args.len() != 3 {
return Err("Usage: azure_export CONTAINER BLOB_NAME FILE".into());
}
let account = env::var("AZURE_STORAGE_ACCOUNT")?;
let key = env::var("AZURE_STORAGE_KEY")?;
let credentials = StorageCredentials::access_key(account.clone(), key);
let service = ClientBuilder::new(account, credentials)
.retry(RetryOptions::none())
.blob_service_client();
timeout(
Duration::from_secs(60),
upload_file(&service, &args[0], &args[1], Path::new(&args[2])),
)
.await??;
println!("Upload confirmed.");
Ok(())
}
#[tokio::main]
async fn main() {
if run().await.is_err() {
eprintln!("Upload was not confirmed; check the input, credentials, container, and destination.");
std::process::exit(1);
}
}
Place the file you want to export at data.bin in the project directory. With credentials supplied
and the private rust-exports container already available, run:
cargo run -- rust-exports 'exports/data.bin' './data.bin'
Cargo resolves the dependencies, builds the program, and runs it. The three arguments are the
container, destination blob name, and local file path. Quote paths containing spaces. A successful
request prints Upload confirmed. and exits zero. The blob is named exports/data.bin; the slash
is part of its name, independent of your local directory structure. Choose a blob name without
leading or trailing slashes: this SDK trims those characters.
The 60-second deadline covers local file reading and the SDK upload, after client construction. Cargo’s dependency installation and compilation are outside it. Even a file below the size ceiling can exceed this deadline on a slow connection.
Error handling
A local read failure, missing configuration, rejected request, or timeout produces a nonzero exit
and the generic Upload was not confirmed diagnostic. The program deliberately avoids printing
credentials or raw SDK responses. Check the local file and its size first, then the selected
account, key, container, and destination. This diagnostic alone does not identify Azure’s refusal
reason or prove that no blob was written.
The IfMatchCondition::NotMatch("*") option sends If-None-Match: *. Azure’s
conditional-header contract
permits the write only when the destination does not exist. Running the command again for an
existing destination fails and preserves its prior contents. Choose another blob name for a new
export instead of deleting an existing object to make the command succeed.
SDK retries are disabled. After a timeout, connection loss, or interruption, Azure may still finish an upload it already received. Stopping the local process does not cancel that remote write. Do not blindly retry or treat an empty listing as proof that a pending write cannot appear later. Inspect the exact destination through an authenticated client and reconcile the first operation before retrying; a later refusal may mean that the first upload succeeded.
Check the stored result
Upload confirmed. means that the SDK received a successful upload response. The program does not
download the blob or compare stored bytes. For an export you need to verify, download that exact
blob through an authenticated client and compare the entire downloaded file with the unchanged
source, including a zero-byte source. Checking the name or byte count alone cannot detect different
content of the same size. Keep the local source until verification is complete.
Container privacy is a separate prerequisite: a successful upload does not establish its access policy. This example provides neither resumable uploads nor a production transfer supervisor. For larger files or identity-based authentication, start with the official SDK’s upload and credential examples rather than extending the legacy pins without a migration review.
