The Rust base64 Crate Mental Model (0.22+)
The Rust base64 crate is not a namespace of free functions like most Base64 libraries. Since version 0.21, it uses the Enginetrait pattern: you pick a pre-configured engine (an alphabet + padding policy), bring the Engine trait into scope, and call .encode()or .decode() on the engine.
This design lets you swap alphabets (standard vs URL-safe vs bcrypt) without changing call sites, and enables no-alloc paths for embedded systems.
The four pre-configured engines in base64::engine::general_purpose:
STANDARD— RFC 4648 §4 (uses+and/, includes padding). HTTP Basic Auth, MIME, data URLs.STANDARD_NO_PAD— Standard alphabet, no=padding.URL_SAFE— RFC 4648 §5 (uses-and_, includes padding). URL parameters that need padding.URL_SAFE_NO_PAD— URL-safe + no padding. JWT headers and payloads.
Setup — Cargo.toml
[dependencies]
base64 = "0.22"
# Optional: for no_std embedded targets
# base64 = { version = "0.22", default-features = false, features = ["alloc"] }Or one-line: cargo add base64. No transitive dependencies.
Encoding a String — The Standard Pattern
use base64::{Engine as _, engine::general_purpose::STANDARD};
fn main() {
let text = "Hello 世界 👋";
// Rust strings are UTF-8 by design — .as_bytes() gives you the UTF-8 bytes
let utf8_bytes: &[u8] = text.as_bytes();
// Encode
let encoded: String = STANDARD.encode(utf8_bytes);
println!("{}", encoded);
// Output: SGVsbG8g5LiW55WMIPCfkYs=
// One-liner
let result = STANDARD.encode(text.as_bytes());
println!("{}", result);
// Or even shorter with byte literal for ASCII-only input
let hello = STANDARD.encode(b"Hello");
println!("{}", hello); // SGVsbG8=
}Decoding Back to a String
use base64::{Engine as _, engine::general_purpose::STANDARD};
fn decode_to_string(encoded: &str) -> Result<String, Box<dyn std::error::Error>> {
let bytes = STANDARD.decode(encoded)?;
let text = String::from_utf8(bytes)?;
Ok(text)
}
fn main() {
match decode_to_string("SGVsbG8g5LiW55WMIPCfkYs=") {
Ok(text) => println!("{}", text), // Hello 世界 👋
Err(e) => eprintln!("decode error: {}", e),
}
}Encoding a File
use std::fs;
use std::io;
use base64::{Engine as _, engine::general_purpose::STANDARD};
use base64::write::EncoderWriter;
// Small files — read entire file into memory
fn encode_small_file(path: &str) -> io::Result<String> {
let data = fs::read(path)?;
Ok(STANDARD.encode(&data))
}
// Large files — streaming encoder writes to any std::io::Write
fn encode_large_file(in_path: &str, out_path: &str) -> io::Result<()> {
let mut input = fs::File::open(in_path)?;
let output = fs::File::create(out_path)?;
let mut encoder = EncoderWriter::new(output, &STANDARD);
io::copy(&mut input, &mut encoder)?;
// CRITICAL: finish() flushes final padding. Without it, output can be truncated.
encoder.finish()?;
Ok(())
}
// Data URL for embedding in HTML/CSS
fn data_url_from_png(path: &str) -> io::Result<String> {
let data = fs::read(path)?;
Ok(format!("data:image/png;base64,{}", STANDARD.encode(&data)))
}URL-Safe and JWT-Style Encoding
use base64::{
Engine as _,
engine::general_purpose::{STANDARD, URL_SAFE, URL_SAFE_NO_PAD},
};
fn main() {
let payload = br#"{"user_id":42,"role":"admin"}"#;
// Standard Base64
let std = STANDARD.encode(payload);
println!("{}", std); // eyJ1c2VyX2lkIjo0Miwicm9sZSI6ImFkbWluIn0=
// URL-safe with padding
let urlsafe = URL_SAFE.encode(payload);
println!("{}", urlsafe);
// JWT-style (URL-safe, no padding)
let jwt = URL_SAFE_NO_PAD.encode(payload);
println!("{}", jwt); // eyJ1c2VyX2lkIjo0Miwicm9sZSI6ImFkbWluIn0
// Decoding back
let decoded_bytes = URL_SAFE_NO_PAD.decode(&jwt).unwrap();
let decoded_text = String::from_utf8(decoded_bytes).unwrap();
println!("{}", decoded_text);
}Encoding a Serde Struct via JSON
// Cargo.toml:
// base64 = "0.22"
// serde = { version = "1", features = ["derive"] }
// serde_json = "1"
use base64::{Engine as _, engine::general_purpose::STANDARD};
use serde::{Deserialize, Serialize};
#[derive(Serialize, Deserialize, Debug)]
struct User {
id: u32,
email: String,
roles: Vec<String>,
joined: String,
}
fn encode_user(user: &User) -> Result<String, Box<dyn std::error::Error>> {
let json = serde_json::to_vec(user)?;
Ok(STANDARD.encode(&json))
}
fn decode_user(encoded: &str) -> Result<User, Box<dyn std::error::Error>> {
let json = STANDARD.decode(encoded)?;
let user: User = serde_json::from_slice(&json)?;
Ok(user)
}
fn main() -> Result<(), Box<dyn std::error::Error>> {
let user = User {
id: 42,
email: "[email protected]".into(),
roles: vec!["admin".into(), "editor".into()],
joined: "2026-01-15".into(),
};
let encoded = encode_user(&user)?;
println!("{}", encoded);
let round_tripped = decode_user(&encoded)?;
println!("{:?}", round_tripped);
Ok(())
}HTTP Basic Auth Header in Rust
use base64::{Engine as _, engine::general_purpose::STANDARD};
fn basic_auth_header(user: &str, password: &str) -> String {
let creds = format!("{}:{}", user, password);
let token = STANDARD.encode(creds.as_bytes());
format!("Basic {}", token)
}
// Usage with reqwest:
// let client = reqwest::Client::new();
// let resp = client
// .get("https://api.example.com/protected")
// .header("Authorization", basic_auth_header("admin", "secret123"))
// .send()
// .await?;
fn main() {
let header = basic_auth_header("admin", "secret123");
println!("{}", header);
// Basic YWRtaW46c2VjcmV0MTIz
}No-Allocation Encoding for Embedded / Performance-Critical Code
use base64::{Engine as _, engine::general_purpose::STANDARD};
use base64::encoded_len;
fn main() {
let input = b"Hello embedded world";
// Pre-size the output buffer at compile time or with encoded_len()
let output_len = encoded_len(input.len(), true).unwrap();
let mut buf = vec![0u8; output_len];
// Encode directly into the pre-allocated buffer — no heap alloc per call
let written = STANDARD.encode_slice(input, &mut buf).unwrap();
buf.truncate(written);
let encoded = std::str::from_utf8(&buf).unwrap();
println!("{}", encoded);
}
// For no_std embedded targets:
// base64 = { version = "0.22", default-features = false, features = ["alloc"] }
// Then use fixed-size arrays instead of Vec:
//
// let mut buf = [0u8; 128];
// let written = STANDARD.encode_slice(input, &mut buf)?;Common Pitfalls in Rust Base64 Code
- Missing the Engine trait import —
STANDARD.encode(bytes)fails to compile with "method not found" unlessuse base64::Engine as _;is in scope. The trait unlocks the method. Newcomers to 0.22 hit this first thing. - Using base64::encode() from old tutorials — The free function was removed in 0.22. Any snippet showing
base64::encode(data)is pre-0.21. Convert to the Engine API before it breaks your build. - Using STANDARD for JWT tokens — JWTs require URL_SAFE_NO_PAD. STANDARD produces
+,/, and=which get percent-encoded in URLs and rejected by JWT verifiers. - Forgetting encoder.finish() on EncoderWriter — The streaming encoder buffers the last 0-2 bytes internally until finish() is called. Without finish(), tail bytes and padding vanish. Prefer explicit
encoder.finish()?over relying on Drop. - Assuming decode never fails —
.decode()returnsResult<Vec<u8>, DecodeError>. Invalid characters, wrong padding, or wrong alphabet all fail. Always handle the error — using.unwrap()on user input is a panic waiting to happen.
Command Line Alternative
For quick one-offs, use the standard base64 CLI (macOS/Linux) orcargo run with a scratch binary:
# System base64
echo -n "Hello" | base64
# Rust one-liner (cargo-script or just a tiny bin project)
cargo new b64_scratch
cd b64_scratch
cargo add base64
# In src/main.rs:
# use base64::{Engine as _, engine::general_purpose::STANDARD};
# fn main() { println!("{}", STANDARD.encode(b"Hello")); }
cargo runKey Facts
- Crate:
- base64 (crates.io) — no transitive dependencies
- Version:
- 0.22+ (uses Engine trait — legacy free functions removed)
- Standard encoding:
- engine::general_purpose::STANDARD.encode(bytes)
- URL-safe encoding:
- engine::general_purpose::URL_SAFE.encode(bytes)
- JWT encoding:
- engine::general_purpose::URL_SAFE_NO_PAD.encode(bytes)
- Streaming:
- base64::write::EncoderWriter — must call .finish() to flush
- No-alloc:
- encode_slice(input, &mut buf) — pre-size with encoded_len()
- no_std support:
- Yes, with default-features = false and alloc feature
Related Base64 Tools
- Base64 Encode Online — general-purpose browser encoder
- Base64 Encode in Python — Python 3 equivalent
- Base64 Encode in Go — Go encoding/base64
- Base64 Encode in JavaScript — Node.js and browser
- Base64 Encode in Java — java.util.Base64
- URL-Safe Base64 — cross-language URL encoding
- JWT Debugger — inspect JWT tokens