Loading page…
Rust walkthroughs
Loading page…
zstd::Encoder and zstd::Decoder streaming interfaces for compression workflows?The zstd crate provides streaming interfaces for both compression and decompression through Encoder and Decoder types. While they share similar patterns, their interfaces have important differences that affect how you structure compression workflows, handle completion, and manage resources.
use std::fs::File;
use std::io::{self, BufWriter, Read, Write};
use zstd::Encoder;
fn compress_file(input_path: &str, output_path: &str) -> io::Result<()> {
let input = File::open(input_path)?;
let output = File::create(output_path)?;
// Encoder wraps the output writer
let mut encoder = Encoder::new(output, 3)?;
// Compression level 3 (default, good balance)
// Read input and write to encoder
let mut reader = input;
io::copy(&mut reader, &mut encoder)?;
// Important: must call finish() to complete compression
encoder.finish()?;
Ok(())
}The encoder wraps an output writer and must be explicitly finished.
use std::fs::File;
use std::io;
use zstd::Decoder;
fn decompress_file(input_path: &str, output_path: &str) -> io::Result<()> {
let input = File::open(input_path)?;
let output = File::create(output_path)?;
// Decoder wraps the input reader
let mut decoder = Decoder::new(input)?;
// Read from decoder, write to output
io::copy(&mut decoder, &output)?;
// No explicit finish needed - decoder completes on drop
// (all data read when io::copy finishes)
Ok(())
}The decoder wraps an input reader and completes automatically.
use std::io::{self, Cursor};
use zstd::{Encoder, Decoder};
fn completion_difference() -> io::Result<()> {
// ENCODER: Must call finish()
let buffer = Vec::new();
let mut encoder = Encoder::new(buffer, 3)?;
encoder.write_all(b"Hello, World!")?;
// WRONG: Just dropping loses data
// drop(encoder); // Data is lost!
// CORRECT: Call finish() to get compressed output
let compressed = encoder.finish()?;
println!("Compressed {} bytes", compressed.len());
// DECODER: No finish() needed
let cursor = Cursor::new(compressed);
let mut decoder = Decoder::new(cursor)?;
let mut decompressed = Vec::new();
io::copy(&mut decoder, &mut decompressed)?;
// Decoder completes automatically when input is exhausted
println!("Decompressed: {}", String::from_utf8_lossy(&decompressed));
Ok(())
}Encoder requires finish(); decoder does not.
use std::io::{self, Write};
use zstd::Encoder;
fn encoder_signature() -> io::Result<()> {
// Encoder<W> where W: Write
// Creates compressed output
let output: Vec<u8> = Vec::new();
let encoder: Encoder<Vec<u8>> = Encoder::new(output, 3)?;
// Encoder implements Write
// Writing to encoder compresses data
// Compressed data goes to inner writer
// finish() returns the inner writer
let mut encoder = Encoder::new(Vec::new(), 3)?;
encoder.write_all(b"data")?;
let compressed: Vec<u8> = encoder.finish()?;
// The compressed Vec<u8> contains the complete zstd frame
Ok(())
}Encoder<W> wraps a Write type and returns it on finish().
use std::io::{self, Cursor, Read};
use zstd::Decoder;
fn decoder_signature() -> io::Result<()> {
// Decoder<R> where R: BufRead
// Note: Requires BufRead, not just Read
let compressed = vec![/* zstd data */];
let cursor = Cursor::new(compressed);
// Decoder::new() requires BufRead
let decoder: Decoder<Cursor<Vec<u8>>> = Decoder::new(cursor)?;
// Decoder implements Read
// Reading from decoder decompresses data
let mut decoder = Decoder::new(Cursor::new(vec![]))?;
let mut output = Vec::new();
decoder.read_to_end(&mut output)?;
// No finish() - decoder is just a Read adapter
Ok(())
}Decoder<R> wraps a BufRead type and implements Read.
use std::io::{self, Cursor};
use zstd::Encoder;
fn compression_levels() -> io::Result<()> {
let data = b"Hello, World! This is a test of compression levels.";
// Level 0: Default (usually level 3)
let mut encoder = Encoder::new(Vec::new(), 0)?;
encoder.write_all(data)?;
let level_0 = encoder.finish()?;
// Level 3: Default, good balance
let mut encoder = Encoder::new(Vec::new(), 3)?;
encoder.write_all(data)?;
let level_3 = encoder.finish()?;
// Level 19: Maximum compression, slower
let mut encoder = Encoder::new(Vec::new(), 19)?;
encoder.write_all(data)?;
let level_19 = encoder.finish()?;
// Level 1: Fast compression, larger output
let mut encoder = Encoder::new(Vec::new(), 1)?;
encoder.write_all(data)?;
let level_1 = encoder.finish()?;
println!("Level 1: {} bytes", level_1.len());
println!("Level 3: {} bytes", level_3.len());
println!("Level 19: {} bytes", level_19.len());
Ok(())
}Encoder accepts compression level; decoder does not need it.
use std::fs::File;
use std::io::{self, BufReader, BufWriter};
use zstd::{Encoder, Decoder};
fn stream_compress(large_input: &str, output: &str) -> io::Result<()> {
let input = File::open(large_input)?;
let output_file = File::create(output)?;
// Use buffered I/O for performance
let reader = BufReader::new(input);
let writer = BufWriter::new(output_file);
// Encoder handles streaming internally
let mut encoder = Encoder::new(writer, 3)?;
io::copy(&mut reader.borrow_mut(), &mut encoder)?;
// finish() writes final zstd frame data
encoder.finish()?;
Ok(())
}
fn stream_decompress(compressed: &str, output: &str) -> io::Result<()> {
let input = File::open(compressed)?;
let output_file = File::create(output)?;
// Decoder requires BufRead
let reader = BufReader::new(input);
let mut writer = BufWriter::new(output_file);
let mut decoder = Decoder::new(reader)?;
io::copy(&mut decoder, &mut writer)?;
// Flush writer to ensure all data is written
writer.flush()?;
Ok(())
}Both support streaming with constant memory usage for large files.
use std::io::{self, Cursor, Read, Write};
use zstd::{Encoder, Decoder};
fn chain_operations() -> io::Result<()> {
let original = b"Data to compress";
// Compress
let mut encoder = Encoder::new(Vec::new(), 3)?;
encoder.write_all(original)?;
let compressed = encoder.finish()?;
// Decompress
let cursor = Cursor::new(compressed);
let mut decoder = Decoder::new(cursor)?;
let mut decompressed = Vec::new();
decoder.read_to_end(&mut decompressed)?;
assert_eq!(original.to_vec(), decompressed);
// Chain: compress to memory, then decompress
let compress = |data: &[u8]| -> io::Result<Vec<u8>> {
let mut encoder = Encoder::new(Vec::new(), 3)?;
encoder.write_all(data)?;
encoder.finish()
};
let decompress = |data: Vec<u8>| -> io::Result<Vec<u8>> {
let mut decoder = Decoder::new(Cursor::new(data))?;
let mut output = Vec::new();
decoder.read_to_end(&mut output)?;
Ok(output)
};
let c = compress(b"test")?;
let d = decompress(c)?;
assert_eq!(d, b"test");
Ok(())
}The finish/return pattern enables chaining operations.
use std::io::{self, Cursor};
use zstd::{Encoder, Decoder};
fn encoder_errors() -> io::Result<()> {
// Encoder errors during write or finish
let mut encoder = Encoder::new(Vec::new(), 3)?;
// Write errors propagate
encoder.write_all(b"data")?;
// finish() can fail if compression fails
let compressed = encoder.finish()?;
Ok(())
}
fn decoder_errors() -> io::Result<()> {
// Decoder errors during read
let invalid_data = vec![0, 1, 2, 3]; // Not valid zstd
let cursor = Cursor::new(invalid_data);
let mut decoder = Decoder::new(cursor)?;
let mut output = Vec::new();
// read_to_end will fail on invalid zstd data
match decoder.read_to_end(&mut output) {
Err(e) => println!("Decompression failed: {}", e),
Ok(_) => println!("Success"),
}
Ok(())
}Encoder errors occur on write/finish; decoder errors occur on read.
use std::io::{self, Cursor, Read, Write};
use zstd::{Encoder, Decoder};
fn buffer_operations() -> io::Result<()> {
// Encoding to a buffer
fn compress_to_buffer(data: &[u8]) -> io::Result<Vec<u8>> {
let mut encoder = Encoder::new(Vec::new(), 3)?;
encoder.write_all(data)?;
encoder.finish()
}
// Decoding from a buffer
fn decompress_from_buffer(compressed: &[u8]) -> io::Result<Vec<u8>> {
let cursor = Cursor::new(compressed);
let mut decoder = Decoder::new(cursor)?;
let mut output = Vec::new();
decoder.read_to_end(&mut output)?;
Ok(output)
}
// Usage
let original = b"Hello, buffer!";
let compressed = compress_to_buffer(original)?;
let decompressed = decompress_from_buffer(&compressed)?;
assert_eq!(original.to_vec(), decompressed);
Ok(())
}Both work naturally with in-memory buffers.
use std::io::{self, Cursor, Read, Write};
use zstd::{Encoder, Decoder};
fn multiple_frames() -> io::Result<()> {
// zstd supports concatenated frames
let mut combined = Vec::new();
// Create first frame
let mut encoder1 = Encoder::new(Vec::new(), 3)?;
encoder1.write_all(b"Frame 1")?;
combined.extend(encoder1.finish()?);
// Create second frame
let mut encoder2 = Encoder::new(Vec::new(), 3)?;
encoder2.write_all(b"Frame 2")?;
combined.extend(encoder2.finish()?);
// Decoder reads all frames by default
let cursor = Cursor::new(&combined);
let mut decoder = Decoder::new(cursor)?;
let mut all_data = Vec::new();
decoder.read_to_end(&mut all_data)?;
// Contains data from both frames
assert_eq!(all_data, b"Frame 1Frame 2");
Ok(())
}Decoder handles multiple concatenated zstd frames automatically.
use std::io;
use zstd::Encoder;
fn encoder_options() -> io::Result<()> {
let mut encoder = Encoder::new(Vec::new(), 3)?;
// Set dictionary for compression
// encoder.set_dictionary(&dict)?;
// Set number of threads for parallel compression
encoder.multithread(4)?; // Use 4 threads
// Set window log (affects memory usage)
// encoder.set_window_log(20)?; // 2^20 window
encoder.write_all(b"data")?;
let compressed = encoder.finish()?;
Ok(())
}Encoder has configuration options; decoder generally does not need them.
use std::io::{self, Cursor};
use zstd::Decoder;
fn decoder_options() -> io::Result<()> {
let compressed = vec![/* zstd data */];
let cursor = Cursor::new(compressed);
// Single-frame mode (error on multiple frames)
let decoder = Decoder::with_buffer(cursor)?;
// Or use new() for multi-frame mode (default)
let cursor2 = Cursor::new(vec![/* ... */]);
let decoder2 = Decoder::new(cursor2)?;
Ok(())
}Decoder options are mainly about frame handling.
use std::io::{self, Write};
use zstd::Encoder;
fn manual_completion() -> io::Result<()> {
let mut encoder = Encoder::new(Vec::new(), 3)?;
encoder.write_all(b"data")?;
// Method 1: finish() returns inner writer
let compressed = encoder.finish()?;
// Method 2: Use into_inner() if you want to access writer
// without completing compression (WARNING: may have partial data)
let mut encoder2 = Encoder::new(Vec::new(), 3)?;
encoder2.write_all(b"data")?;
// let (writer, result) = encoder2.into_inner()?; // Rarely needed
Ok(())
}
fn automatic_completion() {
// Decoder needs no explicit completion
use std::io::Cursor;
use zstd::Decoder;
let compressed = vec![/* ... */];
let cursor = Cursor::new(compressed);
// When dropped, decoder just releases resources
{
let decoder = Decoder::new(cursor).unwrap();
// read data...
} // Automatically cleaned up
}Encoder completion is explicit; decoder cleanup is automatic.
use std::fs::File;
use std::io::{self, BufReader, BufWriter, Read, Write};
use zstd::{Encoder, Decoder};
fn compress_with_progress(input_path: &str, output_path: &str) -> io::Result<()> {
let input = File::open(input_path)?;
let output = File::create(output_path)?;
let mut encoder = Encoder::new(BufWriter::new(output), 3)?;
let mut reader = BufReader::new(input);
// Manual copy with progress
let mut buffer = [0u8; 8192];
let mut total = 0;
loop {
let n = reader.read(&mut buffer)?;
if n == 0 {
break;
}
encoder.write_all(&buffer[..n])?;
total += n;
println!("Compressed {} bytes", total);
}
let _ = encoder.finish()?;
println!("Done");
Ok(())
}
fn decompress_with_validation(input_path: &str) -> io::Result<Vec<u8>> {
let input = File::open(input_path)?;
let reader = BufReader::new(input);
let mut decoder = Decoder::new(reader)?;
let mut output = Vec::new();
let mut buffer = [0u8; 8192];
loop {
let n = decoder.read(&mut buffer)?;
if n == 0 {
break;
}
output.extend_from_slice(&buffer[..n]);
}
Ok(output)
}Both integrate with the standard Read/Write traits.
use std::io::{self, Cursor, Read, Write};
use zstd::{Encoder, Decoder};
fn patterns_comparison() -> io::Result<()> {
// PATTERN: Compress data
fn compress(data: &[u8]) -> io::Result<Vec<u8>> {
let mut encoder = Encoder::new(Vec::new(), 3)?;
encoder.write_all(data)?;
encoder.finish() // Returns compressed Vec<u8>
}
// PATTERN: Decompress data
fn decompress(compressed: &[u8]) -> io::Result<Vec<u8>> {
let cursor = Cursor::new(compressed);
let mut decoder = Decoder::new(cursor)?;
let mut output = Vec::new();
decoder.read_to_end(&mut output)?; // Reads all decompressed data
Ok(output)
}
// PATTERN: Compress to writer
fn compress_to<W: Write>(data: &[u8], writer: W) -> io::Result<W> {
let mut encoder = Encoder::new(writer, 3)?;
encoder.write_all(data)?;
encoder.finish()
}
// PATTERN: Decompress from reader
fn decompress_from<R: Read>(reader: R) -> io::Result<Vec<u8>> {
// Note: Decoder needs BufRead, so wrap if needed
let reader = io::BufReader::new(reader);
let mut decoder = Decoder::new(reader)?;
let mut output = Vec::new();
decoder.read_to_end(&mut output)?;
Ok(output)
}
Ok(())
}The patterns differ mainly in completion handling.
The zstd::Encoder and Decoder streaming interfaces have complementary but asymmetric designs:
Type relationships:
| Type | Wraps | Implements | Returns on completion |
|------|-------|------------|----------------------|
| Encoder<W> | W: Write | Write | W via finish() |
| Decoder<R> | R: BufRead | Read | Nothing (auto-complete) |
Key differences:
| Aspect | Encoder | Decoder |
|--------|---------|---------|
| Direction | Write → compress | Read ← decompress |
| Trait bound | W: Write | R: BufRead |
| Completion | finish() required | Automatic |
| Output | Returns inner writer | Just reads |
| Configuration | Level, threads, dictionary | Frame handling |
| Error timing | On write or finish | On read |
Common patterns:
// Encode pattern
let mut encoder = Encoder::new(writer, level)?;
encoder.write_all(data)?;
let writer = encoder.finish()?;
// Decode pattern
let mut decoder = Decoder::new(reader)?;
decoder.read_to_end(&mut output)?;
// No finish neededMemory characteristics:
| Operation | Memory usage | |-----------|--------------| | Encoding small data | Compressed size + buffers | | Encoding large files | Constant (streaming) | | Decoding small data | Decompressed size | | Decoding large files | Constant (streaming) |
Use streaming interfaces when dealing with data larger than memory or when integrating with other stream-based APIs.