Export files to Microsoft Azure in Rust
This example uploads files of up to 16 MiB to an existing private Azure Blob Storage container. It
uses the pinned, unofficial azure_storage_blobs 0.21.0 API. Microsoft’s
notice for this legacy crate says
it will not receive updates; its API is different from the newer SDK. Treat this as a maintenance
example for that crate, and evaluate the
current Azure SDK for new applications.
Prerequisites
- Rust and Cargo; the example was compiled with Rust 1.98.1 (2021 edition).
- An Azure Storage account that allows Shared Key authentication.
- An existing private container, created by your deployment process.
- A complete local file that will not be modified during the upload.
Project setup
Create the project:
cargo new azure_export --edition 2021 --vcs none
cd azure_export
Replace Cargo.toml with the following. The Tokio filesystem and I/O features are explicit. Commit the generated Cargo.lock in your application to retain transitive dependency versions.
[package]
name = "azure_export"
version = "0.1.0"
edition = "2021"
[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
Set 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. This example uses
StorageCredentials::access_key(); it does not load Azure CLI credentials.
Save the complete program below as src/main.rs.
File upload implementation
The file is read through a 16 MiB limit before any request is sent. This is one buffered
put_block_blob request, not an automatically chunked or parallel upload. The destination blob name
is a separate argument, so local directory names are not accidentally used as blob names.
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);
}
}
With the credentials supplied to the process and the rust-exports container already provisioned, run:
printf 'Example export\n' > data.txt
cargo run -- rust-exports data.txt data.txt
Error handling
Filesystem, credential, SDK, and timeout errors propagate through one application result type. The CLI returns a nonzero exit status without printing credentials or raw server responses. Retries are explicitly disabled to make an ambiguous outcome visible.
The conditional request refuses to overwrite an existing blob. After a timeout or lost response, the server may still have accepted the upload. Inspect the destination before retrying; a retry can fail because the first request created it. A timeout stops waiting locally, not a remote write that Azure already received.
Best practices
Use a dedicated private container and restrict who can read the account key. Do not commit it to source control. Shared Key is the authentication method demonstrated here; switching to identity credentials needs a deliberately configured credential provider and permissions.
Keep local input files immutable until the operation finishes. For larger files, implement staged blocks and a final block-list commit using the pinned crate’s APIs, or choose a maintained SDK with a suitable streaming upload API.
Production considerations
Provision the container separately with public access disabled. This program neither creates containers nor changes their access policy. A missing container or insufficient permission is an upload failure, not a reason to silently create infrastructure.
The conditional write and fixed memory limit make the example’s behavior predictable, but they do not provide resumable uploads. Record confirmed uploads in your application and monitor failures without logging secrets.
For a managed workflow, Transloadit’s Azure export Robot can export processed files to Azure Blob Storage.
