Expand description
Safe Rust Brotli codecs, independently selected with Cargo features.
compression and decompression are enabled by default. Disable default
features and select either codec alongside std or no_std. For example:
mbrotli = { version = "0.4.1", default-features = false, features = ["std", "decompression"] }A disabled codec has no module, API re-exports, dictionaries or I/O adapters.
Shared Backend and RetentionPolicy types exist when either codec is enabled.
With neither codec enabled, the crate exposes no codec API.
§Choose your API
Compression and decompression expose the same core I/O shapes. Pick the shape that
matches how your application already moves bytes; parallel compression is a separate
execution strategy, not another streaming API. Each codec requires its Cargo
feature; reader/writer adapters and parallel compression are unavailable with no_std.
| I/O shape | Compression | Decompression |
|---|---|---|
Return a new Vec<u8> | Compressor::compress | Decompressor::decompress |
| Append to an existing Vec | compress_into | decompress_into |
| Write into a caller-owned slice | compress_to_slice | decompress_to_slice |
Pull output through std::io::Read | Compressor::reader | Decompressor::reader |
Push input through std::io::Write | Compressor::writer | Decompressor::writer |
| Drive input/output incrementally | start → EncoderSession | start → DecoderSession |
§Reuse memory between payloads
Compressor and Decompressor own reusable working state. Keep the codec and your
output buffer alive across operations when allocation reuse matters.
Example: reuse the compressor and output buffer
use mbrotli::{Compressor, EncoderConfig, Quality};
pub fn main() -> Result<(), Box<dyn std::error::Error>> {
let mut encoder = Compressor::new(
EncoderConfig::default().with_quality(Quality::Q5),
)?;
let mut output = Vec::new();
for input in [b"first payload".as_slice(), b"second payload".as_slice()] {
output.clear(); // Keep the allocation; compress_into appends.
let written = encoder.compress_into(input, &mut output)?;
assert_eq!(written, 0..output.len());
}
Ok(())
}§Read and Write streaming
Both codecs provide synchronous adapters for the standard Rust I/O traits. The two adapter shapes are complementary:
reader(...)wraps an inputReadand exposes transformed bytes throughRead.writer(...)wraps an outputWriteand accepts source bytes throughWrite.
That means you can plug Brotli into an existing pull-based or push-based pipeline without first collecting the whole payload in memory.
§Compression I/O
Compressor::reader consumes uncompressed bytes from a Read source and yields
compressed bytes. Compressor::writer accepts uncompressed bytes and writes
compressed bytes to its sink. Encoder writers must be explicitly finished;
dropping one abandons the stream.
Compression with both Read and Write
use mbrotli::{Compressor, EncoderConfig, InputSize, Quality};
use std::io::{Read, Write};
pub fn main() -> Result<(), Box<dyn std::error::Error>> {
let payload = b"streamed payload";
// Pull model: read compressed bytes from an uncompressed source.
let mut encoder = Compressor::new(
EncoderConfig::default().with_quality(Quality::Q5),
)?;
let stream = InputSize::Exact(payload.len() as u64).into();
let mut reader = encoder.reader(&payload[..], stream)?;
let mut compressed_from_reader = Vec::new();
reader.read_to_end(&mut compressed_from_reader)?;
// Push model: write uncompressed bytes into a compressed sink.
let mut encoder = Compressor::new(
EncoderConfig::default().with_quality(Quality::Q5),
)?;
let stream = InputSize::Exact(payload.len() as u64).into();
let mut writer = encoder.writer(Vec::new(), stream)?;
writer.write_all(payload)?;
let compressed_from_writer = writer
.finish()
.map_err(mbrotli::io::FinishError::into_error)?;
assert_eq!(compressed_from_reader, compressed_from_writer);
Ok(())
}flush() makes accepted input decodable without ending the encoder stream, and flush
boundaries can affect compressed bytes. Use finish() when the stream is complete.
§Decompression I/O
Decompressor::reader consumes a compressed Read source and yields the
decompressed payload. Decompressor::writer accepts compressed bytes and
writes the decompressed payload to its sink.
Decompression with both Read and Write
use mbrotli::{DecodeStreamConfig, DecoderConfig, Decompressor};
use std::io::{Read, Write};
fn decode_with_reader(compressed: &[u8]) -> Result<Vec<u8>, Box<dyn std::error::Error>> {
let mut decoder = Decompressor::new(DecoderConfig::default())?;
let mut reader = decoder.reader(compressed, DecodeStreamConfig::default())?;
let mut output = Vec::new();
reader.read_to_end(&mut output)?;
Ok(output)
}
fn decode_with_writer(compressed: &[u8]) -> Result<Vec<u8>, Box<dyn std::error::Error>> {
let mut decoder = Decompressor::new(DecoderConfig::default())?;
let mut writer = decoder.writer(Vec::new(), DecodeStreamConfig::default())?;
writer.write_all(compressed)?;
Ok(writer
.finish()
.map_err(mbrotli::io::FinishError::into_error)?)
}
The decoder adapters use bounded internal buffering. Reader read-ahead can be recovered
with into_parts(), while decoder-writer finalization reports truncated or invalid
input instead of silently accepting an incomplete stream.
§Parallel compression
Parallel compression is independent of the Read/Write adapters. It changes how
one compression job is scheduled: mbrotli splits the input into segments, exposes
work items to the caller, and assembles the completed segments back into one Brotli
stream. The library does not create or own a thread pool.
Example: parallel compression with scoped threads
use mbrotli::compressor::parallel::{
BatchConfig, ParallelCompressor, ParallelConfig, TaskCount,
};
use mbrotli::{EncoderConfig, Quality};
pub fn main() -> Result<(), Box<dyn std::error::Error>> {
let input = vec![b'a'; 8 << 20];
let mut encoder = ParallelCompressor::new(
EncoderConfig::default().with_quality(Quality::Q5),
ParallelConfig::default(),
)?;
let mut batch = encoder.prepare_slice(
&input,
BatchConfig::auto(TaskCount::try_from(2)?),
)?;
let tasks = batch.take_tasks()?;
std::thread::scope(|scope| {
for task in tasks {
scope.spawn(move || task.run());
}
});
let mut output = Vec::new();
let result = batch.finish_into(&mut output)?;
assert_eq!(result.stats.effective_tasks, 2);
assert!(output.len() < input.len());
println!("{} -> {} bytes", input.len(), output.len());
Ok(())
}For fixed segment settings, parallel output is deterministic across task counts, but it can differ in bytes and size from serial compression. The example stages compressed segments in memory; see the parallel guide for budgets, disk staging, file input, and other executors.
§Native decompression
Decompressor provides reusable Vec/slice APIs, incremental sessions, and synchronous
reader/writer adapters. Vec appends are rolled back on failure. The decoder is
currently scalar Rust; SIMD acceleration applies to the encoder.
Configure limits for untrusted input. Numeric budgets are unlimited by default. This example accepts standard windows and sets explicit input, output, and workspace budgets; choose limits appropriate for your application.
use mbrotli::{DecodeLimits, DecoderConfig, Decompressor, WindowLimit};
fn decode_payload(input: &[u8]) -> Result<Vec<u8>, Box<dyn std::error::Error>> {
let limits = DecodeLimits::default()
.with_max_input_bytes(Some(1 << 20))
.with_max_output_bytes(Some(8 << 20))
.with_max_workspace_bytes(Some(32 << 20));
let config = DecoderConfig::default()
.with_window_limit(WindowLimit::standard(24)?)
.with_limits(limits);
let mut decoder = Decompressor::new(config)?;
Ok(decoder.decompress(input)?)
}
The workspace budget excludes caller-owned output and borrowed dictionaries; it is not a total process-memory limit. Retain the decoder across calls when reuse matters. See decoder configuration and semantics and compatibility evidence.
§Structured framing
With compression,experimental, framing::FramedCompressor
owns reusable raw and container storage. Borrowed input preserves resource/metadata order.
Native sessions and one-shot operations support alloc; framed Read/Write
adapters require std APIs.
use mbrotli::framing::{FramedCompressor, FramedInput, FramedItem, FramedResource};
let items = [FramedItem::Resource(FramedResource::from(&b"hello"[..]))];
let mut encoder = FramedCompressor::new(Default::default())?;
let bytes = encoder.compress(FramedInput::from(items.as_slice()))?;
assert_eq!(&bytes[..4], &[0x91, 10, 66, 82]);With decompression,experimental, FramedDecompressor
decodes a container into FramedOutput: resources in wire order
with their metadata, global metadata, and the validated container layout.
Keep the decoder across calls to reuse its storage.
Compression support is optional; the decoder can be built on its own.
To decode a container in bytes:
use mbrotli::framing::FramedDecompressor;
let mut decoder = FramedDecompressor::new(Default::default())?;
let output = decoder.decompress(&bytes)?;
assert_eq!(output.resources.len(), 1);
assert_eq!(output.resources[0].data, b"hello");For incremental input, decoder.start(stream_config)
exposes resource and metadata events with payload fragments. With std APIs,
decoder.framed_reader(source, stream_config)
wraps a BufRead source and exposes next_event().
This event reader preserves resource boundaries; it does not flatten the container
into one Read stream.
FramedDecodeConfig defaults to
InputMode::FramedOnly; InputMode::Auto also
accepts a raw Brotli member. FramedDecodeLimits configures
resource, input, output, metadata and workspace budgets. External dictionary
references use an explicit DictionaryResolver; resource
checksums are recorded without verification.
The owner, one-shot APIs and native sessions also work with no_std and alloc;
FramedReader requires std APIs. See framed decoder mechanics
for validation, dictionaries and streaming semantics.
§Select only the codecs you need
The default feature set is std, compression, and decompression. Disable default
features to select one codec or use no_std with alloc. For an alloc-backed decoder:
[dependencies]
mbrotli = { version = "0.4.1", default-features = false, features = ["no_std", "decompression"] }Add "compression" for both codecs, or use "std" instead of "no_std" for standard
I/O support. no_std requires a global allocator; it excludes I/O adapters, parallel
compression, framed I/O adapters, and profiling, and uses compile-time SIMD selection.
Cargo features are additive: another dependency can re-enable a codec or std.
Leave std and hotpath* disabled throughout the dependency graph for a std-free build.
§Choosing a quality
| Quality | What it does | Typical use |
|---|---|---|
| 0 | One pass, static entropy codes | Fastest, largest output |
| 1 | Two passes, per-block entropy codes | Fast |
| 2 | Greedy matching with the format’s fixed codes | Fast |
| 3 | Greedy matching, one prefix code per stream | Balanced |
| 4 | Adds block splitting and histogram optimisation | Balanced, denser |
| 5 | Adds an extensive search and literal context modelling | Densest of these |
| 6 to 9 | Wider match search, more cached distances, richer context models | Denser, slower |
| 10, 11 | Binary-tree matching and a Zopfli dynamic program | Densest, slowest |
EncoderConfig::default is quality 11, which mirrors the reference
encoder’s default and is far slower than most callers want. For online
compression, say so:
use mbrotli::{Compressor, EncoderConfig, Quality};
let mut encoder = Compressor::new(EncoderConfig::default().with_quality(Quality::Q5))?;
let payload = "the quick brown fox ".repeat(500);
let compressed = encoder.compress(payload.as_bytes())?;
assert!(compressed.len() < payload.len() / 100);§Large Window Brotli
RFC 9841 widens the sliding window past what RFC 7932 can express. Which
header a stream carries is part of the window itself: build one with
Window::standard or Window::large, never by widening a number.
use mbrotli::{Compressor, EncoderConfig, Quality, Window};
let config = EncoderConfig::default()
.with_quality(Quality::Q5)
.with_window(Window::large(30)?);
let mut encoder = Compressor::new(config)?;
let compressed = encoder.compress("large window ".repeat(1000).as_bytes())?;
// The stream carries the RFC 9841 header, so it needs a decoder expecting one.
assert_eq!(compressed[0], 0b0001_0001);Qualities 0, 1 and 2 write distances through a model built for the RFC 7932
alphabet, so Compressor::new refuses a Large Window there rather than
quietly dropping the request.
§Shared dictionaries
RFC 9841 also lets a caller attach up to fifteen LZ77 prefix dictionaries in
front of a stream. A PreparedDictionary
is immutable and holds no per-stream state, so any number of compressors may
borrow one at once without a lock.
use mbrotli::dictionary::DictionaryBuilder;
use mbrotli::{Compressor, EncoderConfig, Quality};
let dictionary = DictionaryBuilder::new()
.add_prefix(&b"HTTP/1.1 200 OK\r\nContent-Type: "[..])
.build()?;
let mut encoder = Compressor::new(EncoderConfig::default().with_quality(Quality::Q5))?;
let payload = b"Content-Type: text/html; charset=utf-8";
assert!(
encoder.compress_with_dictionary(&dictionary, payload)?.len()
< encoder.compress(payload)?.len()
);Below quality five no match finder can consult a dictionary, and one handed
to such a compressor is refused with
EncodeError::DictionaryUnsupportedForQuality rather than ignored: a
stream compressed without the dictionary it was given decodes perfectly
well, which is what would make the mistake invisible.
The experimental feature adds serialized shared dictionaries, custom word
and transform indexes, headerless stream continuations, and the separate
Shared Brotli framing writer. Equivalent-C-streaming byte comparisons do not
cover every extension. Rust API/backend identity and decoder compatibility
remain required for equivalent stream settings.
Re-exports§
pub use decompressor::DecodeFailure;pub use decompressor::DecodeOperation;pub use decompressor::DecodeProgress;pub use decompressor::DecoderSession;pub use decompressor::DecoderStatus;pub use decompressor::Decompressor;pub use decompressor::DecompressorBuilder;pub use decompressor::DecodeConfigError;pub use decompressor::DecodeError;pub use decompressor::DecodeLimits;pub use decompressor::DecodeStreamConfig;pub use decompressor::DecoderConfig;pub use decompressor::InvalidDataKind;pub use decompressor::MemberMode;pub use decompressor::OutputSize;pub use decompressor::WindowLimit;pub use compressor::BlockBits;pub use compressor::BlockSize;pub use compressor::CompressionMode;pub use compressor::Compressor;pub use compressor::CompressorBuilder;pub use compressor::DistanceParams;pub use compressor::EncodeError;pub use compressor::EncoderConfig;pub use compressor::EncoderSession;pub use compressor::EncoderStatus;pub use compressor::InputSize;pub use compressor::LiteralContextMode;pub use compressor::Operation;pub use compressor::Progress;pub use compressor::Quality;pub use compressor::SizeOverflow;pub use compressor::StreamConfig;
Modules§
- compressor
- The compressor: configuration, the encoder itself, and everything it needs.
- decompressor
- Incremental Brotli decompression and explicit resource policies.
- dictionary
- Immutable dictionaries for the enabled codecs.
- io
- Streaming I/O adapters for compression and decompression.
Structs§
- Backend
- A supported execution backend, independent of the SIMD implementation crate.
- Window
- The sliding window: how wide it is, and which header declares it.
Enums§
- Config
Error - Error returned when a configuration cannot be expressed or cannot be used.
- Retention
Policy - What a codec keeps allocated between operations.
- Window
Encoding - Which header a
Windowis written with.