azure-storage-blob-rust
用 Rust 對 Azure Blob Storage 上傳、下載與管理 blob
中文翻譯
---
name: azure-storage-blob-rust
description: |
Rust 版的 Azure Blob Storage library。上傳、下載並管理 blob 和 container。
Triggers: "blob storage rust", "BlobClient rust", "upload blob rust", "download blob rust", "storage container rust", "BlobServiceClient rust".
license: MIT
metadata:
author: Microsoft
package: azure_storage_blob
---
# Rust 版 Azure Blob Storage library
Azure Blob Storage 的 client library,用來上傳、下載並管理 blob 和 container。
在這些情況下使用這個 skill:
- 應用程式要用 Rust 從 Azure Storage 上傳或下載 blob
- 需要建立或管理 blob container
- 需要分頁列出 blob
- blob 操作需要 RBAC 驗證
> **重要:** 只能用 [azure-sdk](https://crates.io/users/azure-sdk) 這個 crates.io 使用者發布的官方 `azure_storage_blob` crate。絕對不要用非官方的 `azure_storage`、`azure_storage_blobs` 或 `azure_sdk_for_rust` 社群 crate。官方 crate 的名稱用底線,而且沒有任何一個版本是 0.21.0。
## 安裝
```sh
cargo add azure_storage_blob azure_identity azure_core tokio futures
```
> 如果你的程式碼直接用到 `azure_core` 的型別(例如 `azure_core::http::Url` 或 `azure_core::http::RequestContent`),要把 `azure_core` 加進 `Cargo.toml`。如果只用 `azure_storage_blob` re-export 出來的東西,直接依賴 `azure_core` 是選用的。
## 環境變數
```bash
AZURE_STORAGE_ACCOUNT=<account-name> # 呼叫者給的是 account 名稱時優先用這個
AZURE_STORAGE_ENDPOINT=https://<account>.blob.core.windows.net/ # 呼叫者給的是完整 endpoint 時的選用替代方案
```
兩個都有的時候,優先用 `AZURE_STORAGE_ACCOUNT` 組出 endpoint,這樣程式碼才會跟常見的評測 prompt 一致。
## 驗證
Rust 的 Azure SDK 程式碼不能用 `DefaultAzureCredential`。Rust 的 identity crate 沒有提供這個型別。
```rust
// 本機開發的正確寫法
use azure_identity::DeveloperToolsCredential;
let credential = DeveloperToolsCredential::new(None)?;
```
```rust
// 在 Rust 裡是錯的:azure_identity 沒有這個型別
use azure_identity::DefaultAzureCredential;
```
```rust
use azure_core::http::Url;
use azure_identity::DeveloperToolsCredential;
use azure_storage_blob::BlobServiceClient;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
// 本機開發:DeveloperToolsCredential。Production:用 ManagedIdentityCredential。
let credential = DeveloperToolsCredential::new(None)?;
let service_url = Url::parse("https://<storage_account_name>.blob.core.windows.net/")?;
let service_client = BlobServiceClient::new(
service_url,
Some(credential),
None,
)?;
// 用名稱衍生出 container 和 blob client。
let container_client = service_client.blob_container_client("<container_name>");
let blob_client = container_client.blob_client("<blob_name>");
Ok(())
}
```
## Client 類型
| Client | 用途 |
| --------------------- | ----------------------------------------- |
| `BlobServiceClient` | Account 層級的操作、列出 container |
| `BlobContainerClient` | Container 操作、列出 blob |
| `BlobClient` | 單一 blob 的操作 |
## 核心工作流程
### 上傳 Blob
```rust
use azure_core::http::{RequestContent, Url};
use azure_identity::DeveloperToolsCredential;
use azure_storage_blob::BlobServiceClient;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
// 本機開發:DeveloperToolsCredential。Production:用 ManagedIdentityCredential。
let credential = DeveloperToolsCredential::new(None)?;
let service_url = Url::parse("https://<storage_account_name>.blob.core.windows.net/")?;
let service_client = BlobServiceClient::new(service_url, Some(credential), None)?;
let blob_client = service_client.blob_client("<container_name>", "<blob_name>");
let data = b"hello world";
blob_client.upload(RequestContent::from(data.to_vec()), None).await?;
Ok(())
}
```
### 下載 Blob/取得屬性
```rust
// 取得 blob 屬性
let props = blob_client.get_properties(None).await?;
// 下載 blob 內容
let response = blob_client.download(None).await?;
let content = String::from_utf8(response.body.collect().await?.into())?;
```
### 刪除 Blob
```rust
blob_client.delete(None).await?;
```
### Container 操作
```rust
use azure_core::http::Url;
use azure_identity::DeveloperToolsCredential;
use azure_storage_blob::BlobServiceClient;
use futures::TryStreamExt as _;
let credential = DeveloperToolsCredential::new(None)?;
let service_url = Url::parse("https://<storage_account_name>.blob.core.windows.net/")?;
let service_client = BlobServiceClient::new(service_url, Some(credential), None)?;
let container_client = service_client.blob_container_client("<container_name>");
// 建立 container
container_client.create(None).await?;
// 列出 blob(這種 client 用法下,pager 直接吐出 BlobItem)
let mut pager = container_client.list_blobs(None)?;
while let Some(blob) = pager.try_next().await? {
let name = blob.name.as_deref().unwrap_or("<unnamed>");
let size = blob
.properties
.as_ref()
.and_then(|properties| properties.content_length);
match size {
Some(size) => println!("Blob: {name} ({size} bytes)"),
None => println!("Blob: {name}"),
}
}
```
`azure_storage_blob` 1.x 不要以為需要像 `for item in &page.blob_items` 這樣的巢狀分頁迴圈。在這種用法下,`try_next()` 吐出來的就已經是你要印的 blob item。
## 錯誤處理
要用程式方式存取 storage 專屬的錯誤代碼,就用 `StorageError`:
```rust
use azure_core::error::ErrorKind;
use azure_storage_blob::StorageError;
use azure_storage_blob::models::StorageErrorCode;
let result = blob_client.download(None).await;
match result {
Ok(response) => {
let content: Vec<u8> = response.body.collect().await?.into();
println!("Downloaded {} bytes", content.len());
}
Err(error) => {
if matches!(error.kind(), ErrorKind::HttpResponse { .. }) {
// 轉成 StorageError 以便用程式存取
let storage_error: StorageError = error.try_into()?;
println!("HTTP Status: {}", storage_error.status_code);
if let Some(error_code) = &storage_error.error_code {
match error_code {
StorageErrorCode::BlobNotFound => {
println!("The blob does not exist.");
}
StorageErrorCode::ContainerNotFound => {
println!("The container does not exist.");
}
StorageErrorCode::AuthorizationFailure => {
println!("Authorization failed. Check RBAC roles.");
}
_ => println!("Storage error: {error_code}"),
}
}
if let Some(request_id) = &storage_error.request_id {
println!("Request ID (for Azure support): {request_id}");
}
} else {
println!("Non-HTTP error: {:?}", error);
}
}
}
```
> **注意:** `StorageError::try_into` 需要擁有所有權的 error 物件,如果傳進去的是 error 的參照,就編譯不過。
## RBAC 角色
用 Entra ID 驗證時,要把下列其中一個角色指派給該身分:
| 角色 | 存取權限 |
| ------------------------------- | -------------------------- |
| `Storage Blob Data Reader` | 唯讀 |
| `Storage Blob Data Contributor` | 讀寫 |
| `Storage Blob Data Owner` | 完整存取,包含 RBAC |
## 最佳實務
1. **用 `cargo add` 管理相依套件,不要直接編輯 `Cargo.toml`。** 新增和移除 Rust SDK 的相依套件,要用 cargo 指令,不要手動改 manifest。
2. **只有在直接 import `azure_core` 型別時才加 `azure_core`。** 如果你的程式碼 import 了 `azure_core::http::Url`、`azure_core::http::RequestContent` 或 `azure_core::error::ErrorKind`,就要加 `azure_core`;否則直接依賴是選用的。
3. 本機開發**用 `DeveloperToolsCredential`**,production 用 **`ManagedIdentityCredential`**。Rust 沒有提供單一的 `DefaultAzureCredential` 型別
4. **絕對不要把憑證寫死**,要用環境變數或 managed identity
5. **用 `RequestContent::from()`** 包住要上傳到 blob 的資料,這樣 SDK 才能正確處理內容
6. **指派 RBAC 角色**,要寫入就要有 "Storage Blob Data Contributor"
7. **重複使用 client**,client 是 thread-safe 的,建立一次,在各個 task 之間共用
8. **優先把 `BlobServiceClient` 當作進入點**,再從它衍生出 container 和 blob client
9. **把很多 storage model 欄位當成選用的。** `blob.name` 是 `Option<String>`,內容長度要用 `blob.properties.as_ref().and_then(|p| p.content_length)` 取得。
10. 當 prompt 或 CI 要求嚴格的 lint 合規時,**在認定工作完成之前先跑 `cargo clippy -- -D warnings`**,連 collapsible `if` 區塊這類風格 lint 也要修,不只是編譯錯誤。
11. 驗證公開 API 的形狀(例如分頁結果)時,**優先參考 crate 的 README/範例**,不要看自動產生的內部型別名稱
12. **讓 `#[non_exhaustive]` 的 SDK model 面向未來**:建構 SDK model/options struct 時,initializer 最後要加 `..Default::default()`(並加上 `#[allow(clippy::needless_update)]`),比對 SDK enum 時要用 `_` 萬用分支,這樣服務之後新增欄位或 variant 時,你的 build 才不會壞
## Rust Blob 常見的坑
- 不要 import `azure_identity::DefaultAzureCredential`,要用 `DeveloperToolsCredential` 或其他真正存在的 Rust credential 型別。
- 不要以為自動產生的內部 model 名稱就代表公開的 pager item 型別,要照這個 crate 文件裡的 `list_blobs` 範例寫。
- 不要直接用 `{}` 印 `blob.name`,它是選用的,要先 unwrap 或提供 fallback。
- 當任務同時要求 `cargo clippy -- -D warnings` 時,不要在 `cargo build` 通過後就停手。
## 參考連結
| 資源 | 連結 |
| ------------- | ------------------------------------------------------------------------------------ |
| API Reference | https://docs.rs/crate/azure_storage_blob/latest |
| crates.io | https://crates.io/crates/azure_storage_blob |
| Source Code | https://github.com/Azure/azure-sdk-for-rust/tree/main/sdk/storage/azure_storage_blob |英文原文
---
name: azure-storage-blob-rust
description: |
Azure Blob Storage library for Rust. Upload, download, and manage blobs and containers.
Triggers: "blob storage rust", "BlobClient rust", "upload blob rust", "download blob rust", "storage container rust", "BlobServiceClient rust".
license: MIT
metadata:
author: Microsoft
package: azure_storage_blob
---
# Azure Blob Storage library for Rust
Client library for Azure Blob Storage — upload, download, and manage blobs and containers.
Use this skill when:
- An app needs to upload or download blobs from Azure Storage in Rust
- You need to create or manage blob containers
- You need to list blobs with pagination
- You need RBAC-based auth for blob operations
> **IMPORTANT:** Only use the official `azure_storage_blob` crate published by the [azure-sdk](https://crates.io/users/azure-sdk) crates.io user. Do NOT use the unofficial `azure_storage`, `azure_storage_blobs`, or `azure_sdk_for_rust` community crates. Official crates use underscores in names and none have version 0.21.0.
## Installation
```sh
cargo add azure_storage_blob azure_identity azure_core tokio futures
```
> If your code uses `azure_core` types directly (for example, `azure_core::http::Url` or `azure_core::http::RequestContent`), add `azure_core` to `Cargo.toml`. If you only use `azure_storage_blob` re-exports, direct `azure_core` dependency is optional.
## Environment Variables
```bash
AZURE_STORAGE_ACCOUNT=<account-name> # Preferred when the caller gives an account name
AZURE_STORAGE_ENDPOINT=https://<account>.blob.core.windows.net/ # Optional alternative when the caller gives a full endpoint
```
When both are available, prefer constructing the endpoint from `AZURE_STORAGE_ACCOUNT` so the code matches common evaluation prompts.
## Authentication
Rust Azure SDK code must not use `DefaultAzureCredential`. The Rust identity crate does not provide that type.
```rust
// Correct for local development
use azure_identity::DeveloperToolsCredential;
let credential = DeveloperToolsCredential::new(None)?;
```
```rust
// Incorrect in Rust: this type does not exist in azure_identity
use azure_identity::DefaultAzureCredential;
```
```rust
use azure_core::http::Url;
use azure_identity::DeveloperToolsCredential;
use azure_storage_blob::BlobServiceClient;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
// Local dev: DeveloperToolsCredential. Production: use ManagedIdentityCredential.
let credential = DeveloperToolsCredential::new(None)?;
let service_url = Url::parse("https://<storage_account_name>.blob.core.windows.net/")?;
let service_client = BlobServiceClient::new(
service_url,
Some(credential),
None,
)?;
// Derive container and blob clients by name.
let container_client = service_client.blob_container_client("<container_name>");
let blob_client = container_client.blob_client("<blob_name>");
Ok(())
}
```
## Client Types
| Client | Purpose |
| --------------------- | ----------------------------------------- |
| `BlobServiceClient` | Account-level operations, list containers |
| `BlobContainerClient` | Container operations, list blobs |
| `BlobClient` | Individual blob operations |
## Core Workflow
### Upload Blob
```rust
use azure_core::http::{RequestContent, Url};
use azure_identity::DeveloperToolsCredential;
use azure_storage_blob::BlobServiceClient;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
// Local dev: DeveloperToolsCredential. Production: use ManagedIdentityCredential.
let credential = DeveloperToolsCredential::new(None)?;
let service_url = Url::parse("https://<storage_account_name>.blob.core.windows.net/")?;
let service_client = BlobServiceClient::new(service_url, Some(credential), None)?;
let blob_client = service_client.blob_client("<container_name>", "<blob_name>");
let data = b"hello world";
blob_client.upload(RequestContent::from(data.to_vec()), None).await?;
Ok(())
}
```
### Download Blob / Get Properties
```rust
// Get blob properties
let props = blob_client.get_properties(None).await?;
// Download blob content
let response = blob_client.download(None).await?;
let content = String::from_utf8(response.body.collect().await?.into())?;
```
### Delete Blob
```rust
blob_client.delete(None).await?;
```
### Container Operations
```rust
use azure_core::http::Url;
use azure_identity::DeveloperToolsCredential;
use azure_storage_blob::BlobServiceClient;
use futures::TryStreamExt as _;
let credential = DeveloperToolsCredential::new(None)?;
let service_url = Url::parse("https://<storage_account_name>.blob.core.windows.net/")?;
let service_client = BlobServiceClient::new(service_url, Some(credential), None)?;
let container_client = service_client.blob_container_client("<container_name>");
// Create container
container_client.create(None).await?;
// List blobs (the pager yields BlobItem values directly for this client pattern)
let mut pager = container_client.list_blobs(None)?;
while let Some(blob) = pager.try_next().await? {
let name = blob.name.as_deref().unwrap_or("<unnamed>");
let size = blob
.properties
.as_ref()
.and_then(|properties| properties.content_length);
match size {
Some(size) => println!("Blob: {name} ({size} bytes)"),
None => println!("Blob: {name}"),
}
}
```
For `azure_storage_blob` 1.x, do not assume you need a nested page loop like `for item in &page.blob_items`. In this usage pattern, `try_next()` already yields the blob item you want to print.
## Error Handling
Use `StorageError` for programmatic access to storage-specific error codes:
```rust
use azure_core::error::ErrorKind;
use azure_storage_blob::StorageError;
use azure_storage_blob::models::StorageErrorCode;
let result = blob_client.download(None).await;
match result {
Ok(response) => {
let content: Vec<u8> = response.body.collect().await?.into();
println!("Downloaded {} bytes", content.len());
}
Err(error) => {
if matches!(error.kind(), ErrorKind::HttpResponse { .. }) {
// Convert to StorageError for programmatic access
let storage_error: StorageError = error.try_into()?;
println!("HTTP Status: {}", storage_error.status_code);
if let Some(error_code) = &storage_error.error_code {
match error_code {
StorageErrorCode::BlobNotFound => {
println!("The blob does not exist.");
}
StorageErrorCode::ContainerNotFound => {
println!("The container does not exist.");
}
StorageErrorCode::AuthorizationFailure => {
println!("Authorization failed. Check RBAC roles.");
}
_ => println!("Storage error: {error_code}"),
}
}
if let Some(request_id) = &storage_error.request_id {
println!("Request ID (for Azure support): {request_id}");
}
} else {
println!("Non-HTTP error: {:?}", error);
}
}
}
```
> **Note:** `StorageError::try_into` requires an owned error object — it will not compile if handed a reference to an error.
## RBAC Roles
For Entra ID auth, assign one of these roles to the identity:
| Role | Access |
| ------------------------------- | -------------------------- |
| `Storage Blob Data Reader` | Read-only |
| `Storage Blob Data Contributor` | Read/write |
| `Storage Blob Data Owner` | Full access including RBAC |
## Best Practices
1. **Use `cargo add` to manage dependencies, never edit `Cargo.toml` directly.** Add and remove Rust SDK dependencies with cargo commands instead of manual manifest edits.
2. **Add `azure_core` only when importing `azure_core` types directly.** If your code imports `azure_core::http::Url`, `azure_core::http::RequestContent`, or `azure_core::error::ErrorKind`, include `azure_core`; otherwise a direct dependency is optional.
3. **Use `DeveloperToolsCredential`** for local dev, **`ManagedIdentityCredential`** for production — Rust does not provide a single `DefaultAzureCredential` type
4. **Never hardcode credentials** — use environment variables or managed identity
5. **Use `RequestContent::from()`** to wrap data for blob uploads — ensures proper content handling by the SDK
6. **Assign RBAC roles** — ensure "Storage Blob Data Contributor" for write access
7. **Reuse clients** — clients are thread-safe; create once, share across tasks
8. **Prefer `BlobServiceClient` as the entry point** and derive container/blob clients from it
9. **Treat many storage model fields as optional.** `blob.name` is an `Option<String>` and content length is accessed via `blob.properties.as_ref().and_then(|p| p.content_length)`.
10. **Run `cargo clippy -- -D warnings` before considering the task complete** when the prompt or CI expects strict lint compliance; fix style lints such as collapsible `if` blocks, not just compiler errors.
11. **Prefer crate README/examples over generated internal type names** when validating public API shapes such as pagination results
12. **Future-proof `#[non_exhaustive]` SDK models** — when constructing SDK model/options structs, end the initializer with `..Default::default()` (add `#[allow(clippy::needless_update)]`) and use a `_` wildcard arm when matching SDK enums, so new service-added fields/variants don't break your build
## Common Rust Blob Pitfalls
- Do not import `azure_identity::DefaultAzureCredential`; use `DeveloperToolsCredential` or another real Rust credential type.
- Do not assume generated internal model names describe the public pager item type; follow the documented `list_blobs` example for this crate.
- Do not print `blob.name` with `{}` directly; unwrap or provide a fallback because it is optional.
- Do not stop after `cargo build` passes when the task also requires `cargo clippy -- -D warnings`.
## Reference Links
| Resource | Link |
| ------------- | ------------------------------------------------------------------------------------ |
| API Reference | https://docs.rs/crate/azure_storage_blob/latest |
| crates.io | https://crates.io/crates/azure_storage_blob |
| Source Code | https://github.com/Azure/azure-sdk-for-rust/tree/main/sdk/storage/azure_storage_blob |