Loading pageā¦
Rust walkthroughs
Loading pageā¦
base64::Engine::decode_slice and decode for in-place buffer decoding?base64::Engine::decode_slice decodes directly into a caller-provided buffer, avoiding allocation when the output buffer is already available or can be reused, while decode allocates a new Vec<u8> for every call, returning owned data. The decode method provides a convenient API for one-off decoding where allocation overhead is acceptable, returning Result<Vec<u8>, DecodeError> with a freshly allocated vector sized exactly for the decoded output. In contrast, decode_slice writes into a pre-allocated slice, returning Result<usize, DecodeError> where the usize indicates how many bytes were written, requiring the caller to size the buffer correctly and slice the result afterward. This distinction matters in performance-sensitive code: decode allocates on every call, while decode_slice enables buffer reuse across multiple decode operations, reducing heap pressure in high-throughput scenarios like decoding many base64 strings in a loop or processing streaming data.
use base64::{Engine as _, engine::general_purpose::STANDARD};
fn main() {
let encoded = "SGVsbG8sIFdvcmxkIQ==";
// decode allocates a new Vec<u8> for the result
let decoded: Vec<u8> = STANDARD.decode(encoded).unwrap();
println!("Decoded: {}", String::from_utf8_lossy(&decoded));
// Output: Decoded: Hello, World!
// The Vec is owned and must be allocated
println!("Length: {} bytes", decoded.len());
}decode returns an owned Vec<u8>, allocating memory for the decoded output.
use base64::{Engine as _, engine::general_purpose::STANDARD};
fn main() {
let encoded = "SGVsbG8sIFdvcmxkIQ==";
// Pre-allocate buffer large enough for decoded output
// Base64 output is ~75% of input length (3/4 ratio)
let mut buffer = vec
![0u8; 100];
// decode_slice writes into the buffer
let bytes_written = STANDARD.decode_slice(encoded, &mut buffer).unwrap();
println!("Bytes written: {}", bytes_written);
println!("Decoded: {}", String::from_utf8_lossy(&buffer[..bytes_written]));
// Output: Decoded: Hello, World!
// Buffer contains more capacity than needed
println!("Buffer capacity: {} bytes", buffer.len());
}decode_slice writes into a provided buffer, returning the number of bytes written.
use base64::{Engine as _, engine::general_purpose::STANDARD};
fn decoded_size_estimate(encoded_len: usize) -> usize {
// Base64 encodes 3 bytes as 4 characters
// So decoded size is roughly 3/4 of encoded length
// Add a small buffer for rounding up
(encoded_len * 3) / 4 + 4
}
fn main() {
let encoded = "SGVsbG8sIFdvcmxkIQ==";
let estimated_size = decoded_size_estimate(encoded.len());
println!("Encoded length: {} bytes", encoded.len());
println!("Estimated decoded size: {} bytes", estimated_size);
let mut buffer = vec
![0u8; estimated_size];
let bytes_written = STANDARD.decode_slice(encoded, &mut buffer).unwrap();
println!("Actual bytes written: {}", bytes_written);
// Output: Encoded length: 20 bytes
// Estimated decoded size: 19 bytes
// Actual bytes written: 13 bytes
}Buffer sizing requires estimating the maximum decoded size; excess capacity is fine.
use base64::{Engine as _, engine::general_purpose::STANDARD};
fn main() {
// Start with base64 string in a mutable buffer
let encoded = "SGVsbG8sIFdvcmxkIQ==";
let mut buffer = encoded.as_bytes().to_vec();
// For in-place decoding, we need a separate buffer
// because base64 decoding shrinks the data
// Allocate output buffer based on input size
let output_size = (buffer.len() * 3) / 4 + 4;
let mut output = vec
![0u8; output_size];
let bytes_written = STANDARD.decode_slice(&buffer, &mut output).unwrap();
// Output now contains the decoded data
output.truncate(bytes_written);
println!("Decoded: {}", String::from_utf8_lossy(&output));
}In-place decoding requires a separate output buffer since base64 decodes to fewer bytes.
use base64::{Engine as _, engine::general_purpose::STANDARD};
fn main() {
let encoded_strings = [
"SGVsbG8=",
"V29ybGQ=",
"UnVzdA==",
];
// Reuse the same buffer for all decodes
let mut buffer = vec
![0u8; 100];
for encoded in encoded_strings {
let bytes_written = STANDARD.decode_slice(encoded, &mut buffer).unwrap();
let decoded = String::from_utf8_lossy(&buffer[..bytes_written]);
println!("Decoded '{}' to '{}'", encoded, decoded);
}
// Buffer is allocated once and reused
}decode_slice enables buffer reuse, avoiding repeated allocations in loops.
use base64::{Engine as _, engine::general_purpose::STANDARD};
fn decode_with_allocation(encoded: &str) -> Vec<u8> {
// Each call allocates a new Vec
STANDARD.decode(encoded).unwrap()
}
fn decode_with_reuse<'a>(encoded: &str, buffer: &'a mut [u8]) -> &'a [u8] {
// Uses provided buffer, no allocation
let len = STANDARD.decode_slice(encoded, buffer).unwrap();
&buffer[..len]
}
fn main() {
let encoded = "SGVsbG8sIFdvcmxkIQ==";
// Method 1: allocate each time
let result1 = decode_with_allocation(encoded);
println!("Allocated: {} bytes", result1.len());
// Method 2: reuse buffer
let mut buffer = vec
![0u8; 100];
let result2 = decode_with_reuse(encoded, &mut buffer);
println!("Reused buffer: {} bytes", result2.len());
// Both produce the same result
assert_eq!(result1, result2);
}decode allocates on every call; decode_slice reuses the buffer.
use base64::{Engine as _, engine::general_purpose::STANDARD, DecodeError};
fn main() {
let invalid_encoded = "SGVsbG8!!!"; // Invalid base64 characters
// decode returns DecodeError wrapped in Result
match STANDARD.decode(invalid_encoded) {
Ok(bytes) => println!("Decoded: {} bytes", bytes.len()),
Err(e) => println!("decode error: {}", e),
}
// decode_slice also returns DecodeError
let mut buffer = vec
![0u8; 100];
match STANDARD.decode_slice(invalid_encoded, &mut buffer) {
Ok(len) => println!("Decoded: {} bytes", len),
Err(e) => println!("decode_slice error: {}", e),
}
}Both methods return DecodeError for invalid input; error types are the same.
use base64::{Engine as _, engine::general_purpose::STANDARD};
fn main() {
let encoded = "SGVsbG8=";
// Small decodes can use stack-allocated arrays
let mut buffer = [0u8; 100];
let bytes_written = STANDARD.decode_slice(encoded, &mut buffer).unwrap();
let decoded = std::str::from_utf8(&buffer[..bytes_written]).unwrap();
println!("Decoded: {}", decoded);
// No heap allocation at all
}decode_slice works with stack arrays, avoiding heap allocation entirely for small outputs.
use base64::{Engine as _, engine::general_purpose::STANDARD};
fn main() {
let encoded = "SGVsbG8sIFdvcmxkIQ==";
// Allocate larger buffer for other uses
let mut buffer = vec
![0u8; 1024];
// Use a slice of the buffer for decoding
let bytes_written = STANDARD.decode_slice(encoded, &mut buffer).unwrap();
// Rest of buffer available for other data
println!("Decoded: {} bytes", bytes_written);
println!("Remaining buffer: {} bytes", buffer.len() - bytes_written);
// Can append more data after decoded content
buffer[bytes_written..bytes_written + 5].copy_from_slice(b"more!");
}decode_slice enables using part of a larger buffer, useful for streaming.
use base64::{Engine as _, engine::general_purpose::STANDARD};
fn main() {
let encoded = "SGVsbG8sIFdvcmxkIQ=="; // Decodes to 13 bytes
// Buffer too small
let mut small_buffer = vec
![0u8; 5];
let result = STANDARD.decode_slice(encoded, &mut small_buffer);
match result {
Ok(len) => println!("Decoded {} bytes", len),
Err(e) => {
// This will fail - buffer too small
println!("Error: buffer too small - {}", e);
}
}
// Correct sizing
let mut buffer = vec
![0u8; 20]; // Plenty of room
let len = STANDARD.decode_slice(encoded, &mut buffer).unwrap();
println!("Successfully decoded {} bytes", len);
}If the buffer is too small, decode_slice returns an error rather than truncating.
use base64::{Engine as _, engine::general_purpose::STANDARD};
fn decoded_size_exact(encoded: &str) -> Option<usize> {
// Calculate exact output size
let len = encoded.len();
// Handle padding
let padding = encoded.chars().rev().take_while(|&c| c == '=').count();
// Base64: 4 chars -> 3 bytes, minus padding
let estimated = (len * 3) / 4 - padding;
// More accurate: count actual padding bytes
let unpadded_len = len - padding;
let exact = (unpadded_len * 3) / 4;
Some(exact)
}
fn main() {
let encoded = "SGVsbG8sIFdvcmxkIQ==";
// Calculate exact size
let exact_size = decoded_size_exact(encoded).unwrap();
println!("Exact decoded size: {} bytes", exact_size);
// Allocate exactly what's needed
let mut buffer = vec
![0u8; exact_size];
let bytes_written = STANDARD.decode_slice(encoded, &mut buffer).unwrap();
assert_eq!(bytes_written, exact_size);
println!("Perfect fit: {} bytes", bytes_written);
}Exact sizing eliminates buffer waste but requires careful calculation.
use base64::{Engine as _, engine::general_purpose::STANDARD};
fn main() {
let encoded_chunks = [
"SGVs",
"bG8s",
"IFdv",
"cmxk",
"IQ==",
];
// For true streaming, use base64::read::DecoderReader
// But decode_slice can process multiple chunks with buffer management
let mut output_buffer = vec
![0u8; 100];
let mut total_written = 0;
// Note: This simplified example doesn't handle partial chunks correctly
// In practice, use DecoderReader for streaming
for chunk in encoded_chunks {
let mut chunk_buffer = vec
![0u8; 50];
let len = STANDARD.decode_slice(chunk, &mut chunk_buffer).unwrap();
output_buffer[total_written..total_written + len]
.copy_from_slice(&chunk_buffer[..len]);
total_written += len;
}
println!("Total decoded: {} bytes", total_written);
println!("Content: {}", String::from_utf8_lossy(&output_buffer[..total_written]));
}For true streaming, use base64::read::DecoderReader rather than manual chunk handling.
use base64::{Engine as _, engine::general_purpose::STANDARD};
struct Base64Decoder {
buffer: Vec<u8>,
}
impl Base64Decoder {
fn new(buffer_size: usize) -> Self {
Self {
buffer: vec
![0u8; buffer_size],
}
}
fn decode(&mut self, encoded: &str) -> &[u8] {
let bytes_written = STANDARD.decode_slice(encoded, &mut self.buffer)
.expect("Decoding failed");
&self.buffer[..bytes_written]
}
fn decode_to_string(&mut self, encoded: &str) -> Result<String, std::string::FromUtf8Error> {
let bytes = self.decode(encoded);
String::from_utf8(bytes.to_vec()).map(|s| s)
}
}
fn main() {
let mut decoder = Base64Decoder::new(1024);
// Same buffer reused for all decodes
let results: Vec<&[u8]> = vec
![
decoder.decode("SGVsbG8="),
decoder.decode("V29ybGQ="),
decoder.decode("UnVzdA=="),
];
for result in results {
println!("Decoded: {}", String::from_utf8_lossy(result));
}
}Encapsulating decode_slice in a struct enables buffer reuse across the application.
use base64::{Engine as _, engine::general_purpose::STANDARD};
fn main() {
// Use decode when:
// - Convenience matters more than performance
// - Decoding happens once or rarely
// - You need owned data anyway
// - Code clarity is priority
let result = STANDARD.decode("SGVsbG8=").unwrap();
println!("Owned Vec: {:?}", result);
// Use decode_slice when:
// - Performance matters (many decodes)
// - You want to reuse buffers
// - Stack allocation is possible
// - Memory allocation should be controlled
// - Integrating with existing buffer pools
let mut buffer = [0u8; 100];
let len = STANDARD.decode_slice("V29ybGQ=", &mut buffer).unwrap();
println!("Buffer: {:?}", &buffer[..len]);
}Choose decode for convenience, decode_slice for performance and buffer control.
use base64::{Engine as _, engine::general_purpose::STANDARD};
// Simulated buffer pool
struct BufferPool {
buffers: Vec<Vec<u8>>,
buffer_size: usize,
}
impl BufferPool {
fn new(pool_size: usize, buffer_size: usize) -> Self {
let buffers = (0..pool_size)
.map(|_| vec
![0u8; buffer_size])
.collect();
Self { buffers, buffer_size }
}
fn get(&mut self) -> Option<BufferHandle> {
self.buffers.pop().map(|buf| BufferHandle { buf })
}
fn return_buffer(&mut self, buf: Vec<u8>) {
self.buffers.push(buf);
}
}
struct BufferHandle {
buf: Vec<u8>,
}
fn main() {
let mut pool = BufferPool::new(5, 1024);
// Decode multiple strings reusing pool buffers
let encoded_data = ["SGVsbG8=", "V29ybGQ=", "UnVzdA=="];
for encoded in encoded_data {
if let Some(mut handle) = pool.get() {
let len = STANDARD.decode_slice(encoded, &mut handle.buf).unwrap();
println!("Decoded: {}", String::from_utf8_lossy(&handle.buf[..len]));
pool.return_buffer(handle.buf);
}
}
// Zero allocations after pool creation
}decode_slice integrates naturally with buffer pools for high-performance applications.
Method comparison:
| Aspect | decode | decode_slice |
|--------|----------|----------------|
| Return type | Result<Vec<u8>, DecodeError> | Result<usize, DecodeError> |
| Allocation | Always allocates new Vec | Uses provided buffer |
| Buffer management | Automatic | Manual |
| Convenience | High | Lower |
| Performance | Allocates per call | Zero allocation |
| Use case | One-off decoding | Repeated decoding |
Buffer sizing for decode_slice:
| Input Size | Recommended Buffer Size |
|------------|------------------------|
| Known exact | (len / 4) * 3 with padding adjustment |
| Unknown | (len * 3) / 4 + 4 (conservative) |
| Maximum bounded | Maximum decoded size |
When to use each:
| Scenario | Recommended Method |
|----------|-------------------|
| Single decode in function | decode |
| Loop with many decodes | decode_slice with reused buffer |
| Stack-allocated output | decode_slice |
| Integration with buffer pool | decode_slice |
| Need owned Vec anyway | decode |
| Streaming decode | DecoderReader |
Key insight: The difference between decode and decode_slice represents the classic trade-off between convenience and control in API design. decode handles all buffer management internally, returning an owned Vec<u8> that's exactly sized for the decoded outputāconvenient for most use cases but with allocation overhead on every call. decode_slice shifts buffer management to the caller: you provide a buffer large enough for the decoded output, and it returns the number of bytes written. This enables buffer reuse across multiple decode operations, stack allocation for small outputs, and integration with buffer pools in performance-sensitive systems. The decode method is appropriate for one-off decoding, configuration files, or any context where the overhead of a single allocation is negligible. decode_slice shines in high-throughput scenarios: decoding thousands of base64 strings in a request handler, processing streaming data, or working in memory-constrained environments where allocation predictability matters. The return type difference reflects the abstraction level: decode returns the decoded data as an owned value, while decode_slice returns metadata (bytes written) about the operation on a buffer you control. Both methods perform the same decoding work and return the same error typesāthe difference is purely in memory management strategy.