heatshrink-rs
A no_std Rust implementation of Heatshrink compression and decompression
for embedded systems.
Heatshrink is an LZSS-based algorithm designed for low-memory environments. It operates with bounded, incremental CPU use, making it suitable for hard real-time systems.
Features
no_std, no allocator required — runs on bare-metal targets.- Configurable parameters — window size
Wand lookaheadLare const generics; any valid(W, L)combination can be used at zero runtime cost. - Idiomatic Rust API —
sink,poll, andfinishreturnResulttypes; no C-style return codes. - Optional search index — enable the
heatshrink-use-indexfeature to significantly speed up compression at the cost of extra memory. embedded-ioadapters — enable theembedded-iofeature to use the encoder and decoder asRead/Writestreams inembedded-iopipelines.- ISC licence — free to use, including for commercial purposes.
Parameters
Both the encoder and decoder are parameterised by const generics:
| Parameter | Description | Constraints |
|---|---|---|
W |
Base-2 log of the LZSS sliding window size | 4 ≤ W ≤ 15 |
L |
Number of bits for back-reference lengths | 3 ≤ L < W |
BUF |
Encoder input buffer size (= 2 << W) |
Must equal 2 << W |
I |
Decoder streaming input buffer size | ≥ 1 |
WIN |
Decoder window buffer size (= 1 << W) |
Must equal 1 << W |
BUF and WIN are redundant parameters required by a current Rust stable
limitation: arithmetic expressions are not yet allowed in const-generic array
sizes. Always set BUF = 2 << W and WIN = 1 << W.
The convenience type aliases [DefaultEncoder] and [DefaultDecoder] use
W=8, L=4 to match the original C library defaults.
Quick Start
Convenience functions (single-call, buffer-to-buffer)
use ;
let input = b"hello heatshrink";
let mut compressed = ;
let mut decompressed = ;
let encoded = encode.unwrap;
let decoded = decode.unwrap;
assert_eq!;
Streaming API
Use the streaming API when processing data in chunks or when working under tight memory constraints.
use ;
// ── Encoding ──────────────────────────────────────────────────────────────────
let input = b"hello heatshrink - streaming example";
let mut compressed = ;
let mut enc = new;
let mut total_in = 0;
let mut total_out = 0;
loop
// ── Decoding ──────────────────────────────────────────────────────────────────
let mut decompressed = ;
let mut dec = new;
let mut dec_in = 0;
let mut dec_out = 0;
loop
assert_eq!;
Custom parameters
use ;
// W=11, L=6 — BUF = 2<<11 = 4096, WIN = 1<<11 = 2048
type MyEncoder = ;
type MyDecoder = ;
embedded-io adapters
Enable the embedded-io feature to use the encoder and decoder as
embedded_io::Read and embedded_io::Write streams:
[]
= { = "...", = ["embedded-io"] }
Four adapters are available in the heatshrink::io module:
| Adapter | Trait | Direction |
|---|---|---|
EncoderWriter<W, ENC> |
Write |
Compress bytes written in → forward to inner Write |
DecoderWriter<W, DEC> |
Write |
Decompress bytes written in → forward to inner Write |
EncoderReader<R, ENC> |
Read |
Read compressed bytes ← pull raw bytes from inner Read |
DecoderReader<R, DEC> |
Read |
Read decompressed bytes ← pull compressed bytes from inner Read |
Call finish() on the Write adapters when all data has been written.
#
Cargo features
| Feature | Default | Description |
|---|---|---|
heatshrink-use-index |
✓ | Enable the search index for faster compression |
embedded-io |
✗ | Enable embedded_io::Read/Write adapters |
Memory usage
With the default parameters W=8, L=4 (measured with core::mem::size_of):
| Configuration | DefaultEncoder |
DefaultDecoder |
|---|---|---|
Without heatshrink-use-index |
552 bytes | 328 bytes |
With heatshrink-use-index |
1576 bytes | 328 bytes |
The search index adds 2 << W × 2 bytes to the encoder: for W=8 that is
512 × 2 = 1024 bytes. Larger window sizes grow the index proportionally —
at W=14 the index alone occupies 32 KB.
Performance
The choice of W and L parameters affects compression ratio, compression
speed, and decompression speed. The graphs below illustrate these trade-offs
for a representative dataset.
Benchmarks were run on a Core i5-8350U @ 1.7 GHz using accelerometer compressed data.

Migrating from 0.4.x
Version 0.4.x had hardcoded parameters (W=8, L=4) and C-style return codes.
The current API differs in the following ways:
- Parameters are now const generics. Use
DefaultEncoder/DefaultDecoderto keep the previous behaviour, or choose any(W, L)pair. sink()returnsResult<usize, SinkError>instead ofHSsinkRes.poll()returnsResult<Poll, PollError>instead ofHSpollRes.finish()returnsFinishinstead ofHSfinishRes.encode()/decode()returnResult<&[u8], CodecError>instead ofResult<&[u8], HSError>.