A slim, #![no_std] compatible ZIP library written in pure Rust.
| Feature | Default | Description |
|---|---|---|
deflate |
on | Deflate decompression via miniz_oxide |
crc32 |
on | CRC-32 checksums via crc32fast |
With default-features = false you get a minimal no_std build — no compression support, no CRC-32 dependency. Bring your own CRC via the Crc32 trait.
use shua_zip::ZipArchive;
let mut archive = ZipArchive::new();
archive.add_file("hello.txt", b"Hello, world!");
archive.add_file("data.bin", vec![0, 1, 2, 3]);
let zip_bytes: Vec<u8> = archive.build().unwrap();
std::fs::write("output.zip", &zip_bytes).unwrap();// Remove by index
if let Some(entry) = archive.remove_file(0) {
println!("Removed: {}", entry.name);
}
// Remove by name
if let Some(idx) = archive.find_by_name("hello.txt") {
archive.remove_file(idx);
}Use build_with_readat to avoid loading existing file data into memory:
use shua_zip::{ZipArchive, ReadAt};
let reader = /* impl ReadAt */;
let mut archive = ZipArchive::new()
.with_binary(&reader)?; // parse existing entries
archive.add_file("new.txt", b"fresh content");
let zip_bytes = archive.build_with_readat(&reader)?;ReadAt is the central trait — implement it for your storage backend:
use shua_zip::{ZipArchive, ReadAt, ZipReadStrategy};
let reader = /* impl ReadAt */;
let archive = ZipArchive::new().with_binary(&reader)?;
// List entries from the central directory
for entry in archive.entries() {
println!("{} ({} bytes)", entry.name, entry.uncompressed_size);
}
// Find by name
if let Some(idx) = archive.find_by_name("hello.txt") {
let data = archive.read_file(idx, &reader)?;
}
// Or use a specific read strategy
let data = archive.read_file_with_strategy(
idx,
&reader,
ZipReadStrategy::Merged,
)?;| Strategy | Reads | Description |
|---|---|---|
Merged |
1–2 | Reads LFH + compressed data in one chunk. Falls back to a second read if data extends beyond the chunk. Recommended for high-latency backends. |
Strict |
2 | Reads and validates the LFH first, then the data at the resolved offset. Safest for malformed files. |
Optimistic |
1 | Skips header verification, jumps straight to the cached data offset. Fastest, may read garbage if offsets are wrong. |
pub trait ReadAt {
type Error: core::error::Error;
fn size(&self) -> u64;
fn read_at(&self, offset: u64, buf: &mut [u8]) -> Result<(), Self::Error>;
}No Seek or Read required — just random-access reads. Works with files, byte slices, HTTP range requests, embedded flash, etc.
archive.register_decompressor(114514, |data| {
// your custom decompression logic
Ok(data.to_vec())
});When crc32 feature is disabled, use add_file_with_crc with your own implementation:
struct MyCrc;
impl Crc32 for MyCrc {
fn hash(data: &[u8]) -> u32 { /* ... */ }
}
archive.add_file_with_crc::<MyCrc>("data.bin", payload);| Method | ID | Read | Write |
|---|---|---|---|
| Stored | 0 | Yes | Yes |
| Deflate | 8 | Yes (opt-in) | No |
| Custom | * | Yes (via register_decompressor) |
No |
# List contents
cargo run --example cli -- list archive.zip
# Extract to a directory
cargo run --example cli -- extract archive.zip ./output/
# Extract to default directory (archive name without extension)
cargo run --example cli -- extract archive.zip
# Create a new archive
cargo run --example cli -- create new.zip file1.txt file2.png
# Add files to an existing archive
cargo run --example cli -- add existing.zip another.txt
# Remove files from an archive
cargo run --example cli -- remove archive.zip old.txt unused.binCross-tested against the zip crate: archives written by either library are correctly read by the other, including deflate-compressed entries, empty files, Unicode filenames, and large payloads. See tests/compat_zip.rs for the full suite.
Benchmarks in benches/compare_zip.rs cover build/parse/read/roundtrip against zip-rs.