How do I work with ZIP archives in Rust?

Walkthrough

The zip crate provides comprehensive support for reading and writing ZIP archives in Rust. ZIP is one of the most widely used archive formats, supporting multiple compression methods including Deflate (the most common), Bzip2, and stored (uncompressed). The crate handles the complexities of the ZIP format, including central directory structures, file headers, CRC checksums, and various encoding issues.

Key concepts:

  1. ZipWriter — creates new ZIP archives with files and directories
  2. ZipArchive — reads existing ZIP archives and extracts files
  3. FileOptions — configures compression method and level for each file
  4. ZipFile — represents a single file within an archive for reading
  5. Compression methods — Stored (no compression), Deflate, Bzip2, Zstd

Code Example

# Cargo.toml
[dependencies]
zip = "0.6"
use std::io::prelude::*;
use std::fs::File;
use zip::ZipWriter;
use zip::write::FileOptions;
 
fn main() -> std::io::Result<()> {
    let file = File::create("archive.zip")?;
    let mut zip = ZipWriter::new(file);
    let options = FileOptions::default().compression_method(zip::CompressionMethod::Deflated);
    
    zip.start_file("hello.txt", options)?;
    zip.write_all(b"Hello, World!")?;
    
    zip.finish()?;
    println!("Created archive.zip");
    Ok(())
}

Creating ZIP Archives

use std::io::prelude::*;
use std::fs::File;
use std::path::Path;
use zip::{ZipWriter, CompressionMethod};
use zip::write::FileOptions;
 
fn main() -> std::io::Result<()> {
    let file = File::create("example.zip")?;
    let mut zip = ZipWriter::new(file);
    
    // Default options with Deflate compression
    let options = FileOptions::default()
        .compression_method(CompressionMethod::Deflated)
        .compression_level(Some(6)); // 0-9, higher = smaller but slower
    
    // Add a simple text file
    zip.start_file("readme.txt", options)?;
    zip.write_all(b"This is a readme file.\nIt contains multiple lines.")?;
    
    // Add another file
    zip.start_file("data/numbers.txt", options)?;
    zip.write_all(b"1\n2\n3\n4\n5")?;
    
    // Add a directory entry
    zip.add_directory("data/", options)?;
    
    // Add a file with no compression
    let stored_options = FileOptions::default()
        .compression_method(CompressionMethod::Stored);
    zip.start_file("raw.bin", stored_options)?;
    zip.write_all(&[0, 1, 2, 3, 4, 5])?;
    
    // Finish writing (important!)
    zip.finish()?;
    
    println!("Created example.zip");
    Ok(())
}

Reading ZIP Archives

use std::fs::File;
use std::io::prelude::*;
use zip::ZipArchive;
 
fn main() -> std::io::Result<()> {
    let file = File::open("example.zip")?;
    let mut archive = ZipArchive::new(file)?;
    
    // List all files in the archive
    println!("Files in archive:");
    for i in 0..archive.len() {
        let file = archive.by_index(i)?;
        println!("  {} ({} bytes)", file.name(), file.size());
    }
    
    // Read a specific file by name
    let mut file = archive.by_name("readme.txt")?;
    let mut contents = String::new();
    file.read_to_string(&mut contents)?;
    println!("\nContents of readme.txt:\n{}", contents);
    
    Ok(())
}

Extracting Files

use std::fs::{self, File};
use std::io::prelude::*;
use std::path::Path;
use zip::ZipArchive;
 
fn extract_archive(archive_path: &str, dest_dir: &str) -> std::io::Result<()> {
    let file = File::open(archive_path)?;
    let mut archive = ZipArchive::new(file)?;
    
    let dest_path = Path::new(dest_dir);
    fs::create_dir_all(dest_path)?;
    
    for i in 0..archive.len() {
        let mut file = archive.by_index(i)?;
        let file_path = dest_path.join(file.name());
        
        if file.name().ends_with('/') {
            // It's a directory
            fs::create_dir_all(&file_path)?;
            println!("Created directory: {}", file_path.display());
        } else {
            // It's a file
            if let Some(parent) = file_path.parent() {
                fs::create_dir_all(parent)?;
            }
            
            let mut outfile = File::create(&file_path)?;
            std::io::copy(&mut file, &mut outfile)?;
            println!("Extracted: {}", file_path.display());
        }
    }
    
    Ok(())
}
 
fn main() -> std::io::Result<()> {
    extract_archive("example.zip", "extracted")?;
    Ok(())
}

Adding Files from Disk

use std::fs::File;
use std::io::prelude::*;
use std::path::Path;
use walkdir::WalkDir;
use zip::{ZipWriter, CompressionMethod};
use zip::write::FileOptions;
 
// Note: Add walkdir = "2.4" to Cargo.toml
 
fn create_zip_from_directory(source_dir: &str, output_path: &str) -> std::io::Result<()> {
    let file = File::create(output_path)?;
    let mut zip = ZipWriter::new(file);
    let options = FileOptions::default()
        .compression_method(CompressionMethod::Deflated)
        .compression_level(Some(6));
    
    let source_path = Path::new(source_dir);
    
    for entry in WalkDir::new(source_dir)
        .into_iter()
        .filter_map(|e| e.ok())
    {
        let path = entry.path();
        let name = path.strip_prefix(source_path).unwrap().to_str().unwrap();
        
        if path.is_file() {
            zip.start_file(name, options)?;
            let mut f = File::open(path)?;
            std::io::copy(&mut f, &mut zip)?;
            println!("Added: {}", name);
        } else if !name.is_empty() {
            zip.add_directory(&format!("{}/", name), options)?;
            println!("Added directory: {}/", name);
        }
    }
    
    zip.finish()?;
    println!("Created {}", output_path);
    Ok(())
}
 
fn main() -> std::io::Result<()> {
    // Create a test directory first
    std::fs::create_dir_all("test_dir/subdir")?;
    std::fs::write("test_dir/file1.txt", "Content of file 1")?;
    std::fs::write("test_dir/subdir/file2.txt", "Content of file 2")?;
    
    create_zip_from_directory("test_dir", "backup.zip")?;
    Ok(())
}

File Metadata and Options

use std::io::prelude::*;
use std::fs::File;
use zip::{ZipWriter, ZipArchive, CompressionMethod};
use zip::write::FileOptions;
use zip::DateTime;
 
fn main() -> std::io::Result<()> {
    let file = File::create("metadata.zip")?;
    let mut zip = ZipWriter::new(file);
    
    // Set various file options
    let options = FileOptions::default()
        .compression_method(CompressionMethod::Deflated)
        .compression_level(Some(9)) // Maximum compression
        .last_modified_time(DateTime::from_date_and_time(2024, 1, 15, 10, 30, 0)?)
        .unix_permissions(0o644) // rw-r--r--
        .large_file(false); // Use ZIP64 for large files
    
    zip.start_file("document.txt", options)?;
    zip.write_all(b"Document with metadata")?;
    
    // File with password (AES encryption requires feature flag)
    // Note: Basic ZipCrypto is supported by default
    let encrypted_options = FileOptions::default()
        .compression_method(CompressionMethod::Deflated)
        .with_deprecated_encryption(b"secret_password");
    
    zip.start_file("secret.txt", encrypted_options)?;
    zip.write_all(b"Encrypted content")?;
    
    zip.finish()?;
    
    // Read back metadata
    let file = File::open("metadata.zip")?;
    let mut archive = ZipArchive::new(file)?;
    
    for i in 0..archive.len() {
        let file = archive.by_index(i)?;
        println!("File: {}", file.name());
        println!("  Size: {} bytes", file.size());
        println!("  Compressed: {} bytes", file.compressed_size());
        println!("  Compression: {:?}", file.compression());
        println!("  Modified: {:?}", file.last_modified());
        println!("  Encrypted: {}", file.encrypted());
    }
    
    Ok(())
}

Compression Methods

use std::io::prelude::*;
use std::fs::File;
use zip::{ZipWriter, ZipArchive, CompressionMethod};
use zip::write::FileOptions;
 
fn main() -> std::io::Result<()> {
    let file = File::create("compression_test.zip")?;
    let mut zip = ZipWriter::new(file);
    
    let data = b"This is some repeated text that will compress well. "
        .repeat(100);
    
    // Stored (no compression)
    let stored_options = FileOptions::default()
        .compression_method(CompressionMethod::Stored);
    zip.start_file("stored.txt", stored_options)?;
    zip.write_all(&data)?;
    
    // Deflate (standard)
    let deflate_options = FileOptions::default()
        .compression_method(CompressionMethod::Deflated)
        .compression_level(Some(6));
    zip.start_file("deflated.txt", deflate_options)?;
    zip.write_all(&data)?;
    
    // Deflate with maximum compression
    let deflate_max_options = FileOptions::default()
        .compression_method(CompressionMethod::Deflated)
        .compression_level(Some(9));
    zip.start_file("deflated_max.txt", deflate_max_options)?;
    zip.write_all(&data)?;
    
    // Bzip2 (requires feature flag)
    // Add to Cargo.toml: zip = { version = "0.6", features = ["bzip2"] }
    #[cfg(feature = "bzip2")]
    {
        let bzip2_options = FileOptions::default()
            .compression_method(CompressionMethod::Bzip2);
        zip.start_file("bzip2.txt", bzip2_options)?;
        zip.write_all(&data)?;
    }
    
    // Zstd (requires feature flag)
    // Add to Cargo.toml: zip = { version = "0.6", features = ["zstd"] }
    #[cfg(feature = "zstd")]
    {
        let zstd_options = FileOptions::default()
            .compression_method(CompressionMethod::Zstd);
        zip.start_file("zstd.txt", zstd_options)?;
        zip.write_all(&data)?;
    }
    
    zip.finish()?;
    
    // Compare sizes
    let file = File::open("compression_test.zip")?;
    let archive = ZipArchive::new(file)?;
    
    println!("Compression comparison (original: {} bytes):", data.len());
    for i in 0..archive.len() {
        let file = archive.by_index(i)?;
        println!("  {}: {} bytes ({:.1}% of original)", 
            file.name(), 
            file.compressed_size(),
            (file.compressed_size() as f64 / data.len() as f64) * 100.0
        );
    }
    
    Ok(())
}

Streaming ZIP Creation

use std::io::prelude::*;
use std::fs::File;
use zip::{ZipWriter, CompressionMethod};
use zip::write::FileOptions;
 
// Writing ZIP to a Vec (in memory)
fn create_zip_in_memory() -> Vec<u8> {
    let mut buffer = Vec::new();
    {
        let mut cursor = std::io::Cursor::new(&mut buffer);
        let mut zip = ZipWriter::new(&mut cursor);
        
        let options = FileOptions::default()
            .compression_method(CompressionMethod::Deflated);
        
        zip.start_file("file1.txt", options).unwrap();
        zip.write_all(b"Content 1").unwrap();
        
        zip.start_file("file2.txt", options).unwrap();
        zip.write_all(b"Content 2").unwrap();
        
        zip.finish().unwrap();
    }
    buffer
}
 
// Reading ZIP from memory
fn read_zip_from_memory(data: &[u8]) -> zip::ZipArchive<std::io::Cursor<&[u8]>> {
    let cursor = std::io::Cursor::new(data);
    ZipArchive::new(cursor).unwrap()
}
 
fn main() -> std::io::Result<()> {
    // Create in memory
    let zip_data = create_zip_in_memory();
    println!("ZIP size in memory: {} bytes", zip_data.len());
    
    // Read from memory
    let mut archive = read_zip_from_memory(&zip_data);
    
    for i in 0..archive.len() {
        let mut file = archive.by_index(i)?;
        let mut contents = String::new();
        file.read_to_string(&mut contents)?;
        println!("{}: {}", file.name(), contents);
    }
    
    Ok(())
}

Appending to Existing Archives

use std::fs::{self, File, OpenOptions};
use std::io::prelude::*;
use std::path::Path;
use zip::{ZipWriter, ZipArchive, CompressionMethod};
use zip::write::FileOptions;
 
fn append_to_archive(archive_path: &str, filename: &str, content: &[u8]) -> std::io::Result<()> {
    // Read existing archive
    let file = File::open(archive_path)?;
    let mut existing = ZipArchive::new(file)?;
    
    // Create new archive
    let temp_path = format!("{}.tmp", archive_path);
    let temp_file = File::create(&temp_path)?;
    let mut new_zip = ZipWriter::new(temp_file);
    let options = FileOptions::default()
        .compression_method(CompressionMethod::Deflated);
    
    // Copy existing files
    for i in 0..existing.len() {
        let mut file = existing.by_index(i)?;
        let name = file.name().to_string();
        
        if name != filename {
            new_zip.start_file(&name, options.clone())?;
            std::io::copy(&mut file, &mut new_zip)?;
        }
    }
    
    // Add new file
    new_zip.start_file(filename, options)?;
    new_zip.write_all(content)?;
    
    new_zip.finish()?;
    
    // Replace original with new
    fs::rename(&temp_path, archive_path)?;
    
    Ok(())
}
 
fn main() -> std::io::Result<()> {
    // Create initial archive
    let file = File::create("append.zip")?;
    let mut zip = ZipWriter::new(file);
    let options = FileOptions::default()
        .compression_method(CompressionMethod::Deflated);
    
    zip.start_file("original.txt", options.clone())?;
    zip.write_all(b"Original content")?;
    zip.finish()?;
    
    // Append new file
    append_to_archive("append.zip", "added.txt", b"Added content")?;
    
    // Verify
    let file = File::open("append.zip")?;
    let archive = ZipArchive::new(file)?;
    
    println!("Files in archive:");
    for i in 0..archive.len() {
        let file = archive.by_index(i)?;
        println!("  {}", file.name());
    }
    
    Ok(())
}

Reading Encrypted Files

use std::fs::File;
use std::io::prelude::*;
use zip::ZipArchive;
 
fn main() -> std::io::Result<()> {
    // First create an encrypted archive
    {
        use zip::{ZipWriter, CompressionMethod};
        use zip::write::FileOptions;
        
        let file = File::create("encrypted.zip")?;
        let mut zip = ZipWriter::new(file);
        
        let options = FileOptions::default()
            .compression_method(CompressionMethod::Deflated)
            .with_deprecated_encryption(b"my_password");
        
        zip.start_file("secret.txt", options)?;
        zip.write_all(b"This is secret content")?;
        
        zip.finish()?;
    }
    
    // Read encrypted archive
    let file = File::open("encrypted.zip")?;
    let mut archive = ZipArchive::new(file)?;
    
    // Try without password (fails)
    // let mut file = archive.by_name("secret.txt")?; // Error: password required
    
    // Read with password
    let mut file = archive.by_name_decrypt("secret.txt", b"my_password")?;
    let mut contents = String::new();
    file.read_to_string(&mut contents)?;
    
    println!("Decrypted content: {}", contents);
    
    Ok(())
}

Working with ZIP File Information

use std::fs::File;
use std::io::prelude::*;
use zip::ZipArchive;
 
fn print_zip_info(archive_path: &str) -> std::io::Result<()> {
    let file = File::open(archive_path)?;
    let archive = ZipArchive::new(file)?;
    
    println!("Archive: {}", archive_path);
    println!("Number of files: {}", archive.len());
    println!();
    
    let mut total_size = 0u64;
    let mut total_compressed = 0u64;
    
    for i in 0..archive.len() {
        let file = archive.by_index(i)?;
        
        println!("File: {}", file.name());
        println!("  Size: {} bytes", file.size());
        println!("  Compressed size: {} bytes", file.compressed_size());
        println!("  Compression method: {:?}", file.compression());
        println!("  Modified: {:?}", file.last_modified());
        println!("  Is directory: {}", file.is_dir());
        println!("  Is file: {}", file.is_file());
        println!("  Encrypted: {}", file.encrypted());
        
        // Check for comments
        if !file.comment().is_empty() {
            println!("  Comment: {}", file.comment());
        }
        
        total_size += file.size();
        total_compressed += file.compressed_size();
        println!();
    }
    
    println!("Total:");
    println!("  Uncompressed: {} bytes", total_size);
    println!("  Compressed: {} bytes", total_compressed);
    println!("  Ratio: {:.1}%", 
        (total_compressed as f64 / total_size as f64) * 100.0
    );
    
    Ok(())
}
 
fn main() -> std::io::Result<()> {
    // Create a test archive first
    {
        use zip::{ZipWriter, CompressionMethod};
        use zip::write::FileOptions;
        
        let file = File::create("info_test.zip")?;
        let mut zip = ZipWriter::new(file);
        let options = FileOptions::default()
            .compression_method(CompressionMethod::Deflated);
        
        zip.start_file("file1.txt", options.clone())?;
        zip.write_all(b"Content of file 1")?;
        
        zip.add_directory("subdir/", options.clone())?;
        
        zip.start_file("subdir/file2.txt", options)?;
        zip.write_all(b"Content of file 2 in subdir")?;
        
        zip.finish()?;
    }
    
    print_zip_info("info_test.zip")?;
    Ok(())
}

Real-World Example: Backup Tool

use std::fs::{self, File};
use std::io::prelude::*;
use std::path::{Path, PathBuf};
use chrono::Local;
use zip::{ZipWriter, ZipArchive, CompressionMethod};
use zip::write::FileOptions;
 
// Note: Add chrono = "0.4" to Cargo.toml
 
struct BackupConfig {
    source_dir: PathBuf,
    backup_file: PathBuf,
    compression_level: Option<i32>,
    exclude_patterns: Vec<String>,
}
 
fn should_exclude(path: &Path, patterns: &[String]) -> bool {
    let path_str = path.to_string_lossy();
    patterns.iter().any(|p| path_str.contains(p))
}
 
fn create_backup(config: &BackupConfig) -> std::io::Result<()> {
    let file = File::create(&config.backup_file)?;
    let mut zip = ZipWriter::new(file);
    
    let options = FileOptions::default()
        .compression_method(CompressionMethod::Deflated)
        .compression_level(config.compression_level);
    
    let mut file_count = 0;
    let mut total_bytes = 0u64;
    
    fn add_dir<P: AsRef<Path>>(
        zip: &mut ZipWriter<File>,
        dir: P,
        base: &Path,
        options: FileOptions,
        exclude: &[String],
        stats: &mut (usize, u64),
    ) -> std::io::Result<()> {
        for entry in fs::read_dir(dir)? {
            let entry = entry?;
            let path = entry.path();
            let relative = path.strip_prefix(base).unwrap();
            
            if should_exclude(&path, exclude) {
                continue;
            }
            
            if path.is_dir() {
                let name = relative.to_str().unwrap();
                zip.add_directory(&format!("{}/", name), options.clone())?;
                add_dir(zip, &path, base, options.clone(), exclude, stats)?;
            } else {
                let name = relative.to_str().unwrap();
                zip.start_file(name, options.clone())?;
                
                let mut file = File::open(&path)?;
                let bytes = std::io::copy(&mut file, zip)?;
                
                stats.0 += 1;
                stats.1 += bytes;
            }
        }
        Ok(())
    }
    
    let mut stats = (0, 0u64);
    add_dir(
        &mut zip,
        &config.source_dir,
        &config.source_dir,
        options,
        &config.exclude_patterns,
        &mut stats,
    )?;
    
    zip.finish()?;
    
    file_count = stats.0;
    total_bytes = stats.1;
    
    let backup_size = fs::metadata(&config.backup_file)?.len();
    
    println!("Backup created: {}", config.backup_file.display());
    println!("  Files: {}", file_count);
    println!("  Original size: {} bytes", total_bytes);
    println!("  Backup size: {} bytes", backup_size);
    println!("  Compression ratio: {:.1}%", 
        (backup_size as f64 / total_bytes as f64) * 100.0
    );
    
    Ok(())
}
 
fn restore_backup(backup_file: &str, dest_dir: &str) -> std::io::Result<()> {
    let file = File::open(backup_file)?;
    let mut archive = ZipArchive::new(file)?;
    
    let dest = Path::new(dest_dir);
    fs::create_dir_all(dest)?;
    
    for i in 0..archive.len() {
        let mut file = archive.by_index(i)?;
        let outpath = dest.join(file.name());
        
        if file.name().ends_with('/') {
            fs::create_dir_all(&outpath)?;
        } else {
            if let Some(p) = outpath.parent() {
                fs::create_dir_all(p)?;
            }
            let mut outfile = File::create(&outpath)?;
            std::io::copy(&mut file, &mut outfile)?;
        }
    }
    
    println!("Restored {} files to {}", archive.len(), dest_dir);
    Ok(())
}
 
fn main() -> std::io::Result<()> {
    // Create test directory
    fs::create_dir_all("backup_test/subdir")?;
    fs::write("backup_test/file1.txt", "Important data")?;
    fs::write("backup_test/subdir/file2.txt", "More important data")?;
    
    let config = BackupConfig {
        source_dir: PathBuf::from("backup_test"),
        backup_file: PathBuf::from(format!("backup_{}.zip", Local::now().format("%Y%m%d_%H%M%S"))),
        compression_level: Some(9),
        exclude_patterns: vec![".git".to_string(), "target".to_string()],
    };
    
    create_backup(&config)?;
    
    Ok(())
}

Real-World Example: Log Rotator

use std::fs::{self, File};
use std::io::prelude::*;
use std::path::PathBuf;
use zip::{ZipWriter, CompressionMethod};
use zip::write::FileOptions;
 
fn rotate_logs(log_dir: &str, archive_name: &str) -> std::io::Result<()> {
    let log_path = PathBuf::from(log_dir);
    let archive_path = log_path.join(archive_name);
    
    let file = File::create(&archive_path)?;
    let mut zip = ZipWriter::new(file);
    
    let options = FileOptions::default()
        .compression_method(CompressionMethod::Deflated)
        .compression_level(Some(6));
    
    let mut archived_count = 0;
    
    for entry in fs::read_dir(&log_path)? {
        let entry = entry?;
        let path = entry.path();
        
        // Skip directories and the archive itself
        if path.is_dir() || path.extension().map(|e| e == "zip").unwrap_or(false) {
            continue;
        }
        
        let filename = path.file_name().unwrap().to_str().unwrap();
        
        // Add to archive
        zip.start_file(filename, options)?;
        let mut log_file = File::open(&path)?;
        std::io::copy(&mut log_file, &mut zip)?;
        
        // Delete original
        fs::remove_file(&path)?;
        archived_count += 1;
        println!("Archived: {}", filename);
    }
    
    zip.finish()?;
    
    println!("Archived {} log files to {}", archived_count, archive_path.display());
    Ok(())
}
 
fn main() -> std::io::Result<()> {
    // Create test logs
    fs::create_dir_all("logs")?;
    fs::write("logs/app.log", "2024-01-15 Log entry 1\n2024-01-15 Log entry 2")?;
    fs::write("logs/error.log", "2024-01-15 Error: something failed")?;
    fs::write("logs/access.log", "2024-01-15 GET /index.html")?;
    
    rotate_logs("logs", "logs_archive.zip")?;
    
    Ok(())
}

Error Handling

use std::fs::File;
use std::io::prelude::*;
use zip::ZipArchive;
use zip::result::ZipError;
 
fn read_zip_safely(path: &str) -> Result<Vec<(String, Vec<u8>)>, Box<dyn std::error::Error>> {
    let file = File::open(path).map_err(|e| format!("Failed to open file: {}", e))?;
    
    let mut archive = ZipArchive::new(file)
        .map_err(|e| format!("Invalid ZIP archive: {}", e))?;
    
    let mut files = Vec::new();
    
    for i in 0..archive.len() {
        let mut file = archive.by_index(i)?;
        let name = file.name().to_string();
        
        // Skip directories
        if file.is_dir() {
            continue;
        }
        
        // Check for reasonable file size
        if file.size() > 100_000_000 { // 100 MB
            return Err(format!("File {} is too large", name).into());
        }
        
        let mut contents = Vec::new();
        file.read_to_end(&mut contents)?;
        files.push((name, contents));
    }
    
    Ok(files)
}
 
fn extract_specific_file(path: &str, filename: &str) -> Result<Vec<u8>, ZipError> {
    let file = File::open(path)?;
    let mut archive = ZipArchive::new(file)?;
    
    let mut file = archive.by_name(filename)?;
    let mut contents = Vec::new();
    file.read_to_end(&mut contents)?;
    
    Ok(contents)
}
 
fn main() {
    match read_zip_safely("nonexistent.zip") {
        Ok(files) => {
            for (name, contents) in files {
                println!("{}: {} bytes", name, contents.len());
            }
        }
        Err(e) => println!("Error: {}", e),
    }
}

Unicode File Names

use std::fs::File;
use std::io::prelude::*;
use zip::{ZipWriter, ZipArchive, CompressionMethod};
use zip::write::FileOptions;
 
fn main() -> std::io::Result<()> {
    let file = File::create("unicode.zip")?;
    let mut zip = ZipWriter::new(file);
    let options = FileOptions::default()
        .compression_method(CompressionMethod::Deflated);
    
    // Files with Unicode names
    zip.start_file("文档/说明.txt", options.clone())?;
    zip.write_all("中文内容".as_bytes())?;
    
    zip.start_file("ドキュメント/説明.txt", options.clone())?;
    zip.write_all("日本語コンテンツ".as_bytes())?;
    
    zip.start_file("документ/описание.txt", options.clone())?;
    zip.write_all("Русский контент".as_bytes())?;
    
    zip.start_file("emoji/😀.txt", options)?;
    zip.write_all("Emoji support! 🎉".as_bytes())?;
    
    zip.finish()?;
    
    // Read back
    let file = File::open("unicode.zip")?;
    let archive = ZipArchive::new(file)?;
    
    println!("Files with Unicode names:");
    for i in 0..archive.len() {
        let file = archive.by_index(i)?;
        println!("  {}", file.name());
    }
    
    Ok(())
}

Summary

  • Use ZipWriter to create archives, ZipArchive to read them
  • FileOptions configures compression method and level per file
  • Common compression methods: Deflated (standard), Stored (none), Bzip2, Zstd
  • Always call finish() on ZipWriter to finalize the archive
  • Use by_name() or by_index() to access files in an archive
  • is_dir() and is_file() distinguish files from directories
  • with_deprecated_encryption() adds basic password protection (ZipCrypto)
  • For AES encryption, enable the aes-crypto feature
  • Handle large files with .large_file(true) for ZIP64 support
  • Unicode filenames are supported automatically
  • Set unix_permissions() to preserve file permissions
  • Use last_modified_time() to set timestamps
  • Streaming works with any Write implementation (including Vec<u8>)
  • Error handling covers invalid archives, missing files, and password requirements