excelstream
🦀 High-performance Rust library for Excel import/export with streaming support
✨ What's New in v0.6.2:
- ⬆️ Upgraded zip crate - Now using zip 6.0 (latest) with better compression and bug fixes
- 🗑️ Removed deprecated code - Deleted
fix_xlsx_zip_order()function (no longer needed)- 💾 Better Memory - Improved by ~2%: All methods now use 55 MB (was 56 MB)
v0.6.1 Features:
- 🐛 Leading Zero Bug Fixed - String numbers like "090899" now preserve leading zeros
- 🔧 Improved Type Handling -
write_row()treats all values as strings (no auto number detection)- ✅ Better Type Control - Use
write_row_typed()withCellValue::Int/Floatfor numbers- 🧠 Hybrid SST in All Methods - Memory optimization applied to all write functions
- 💾 Memory Verified - All methods stay under 60 MB with realistic data (<80 MB target)
v0.5.1 Features:
- 🗜️ Compression Level Configuration - Control ZIP compression levels (0-9) for speed vs size trade-offs
- ⚙️ Flexible API - Set compression at workbook creation or anytime during writing
- ⚡ Fast Mode - Level 1 compression: 2x faster, suitable for development and testing
- 📦 Balanced Mode - Level 3 compression: Good balance between speed and file size
- 💾 Production Mode - Level 6 compression (default): Best file size for production exports
- 🔧 Memory Optimization - PostgreSQL streaming with optimized batch sizes (500 rows)
v0.5.0 Features:
- 🚀 Hybrid SST Optimization - Intelligent selective deduplication for optimal memory usage
- 💾 Ultra-Low Memory - 15-25 MB for 1M rows (was 125 MB), 89% reduction!
- ⚡ 58% Faster - 25K+ rows/sec with hybrid SST strategy
- 🎯 Smart Detection - Numbers inline, long strings inline, only short repeated strings deduplicated
✨ Features
- 🚀 Streaming Read - Process large Excel files without loading entire file into memory
- 💾 Ultra-Low Memory Write - Write millions of rows with only 15-25 MB memory usage (89% reduction!)
- ⚡ High Performance - 25K+ rows/sec with hybrid SST optimization (58% faster!)
- 🧠 Hybrid SST - Intelligent deduplication: numbers inline, long strings inline, only short repeated strings deduplicated
- 🗜️ Compression Control - Configure ZIP compression levels (0-9) for speed vs size optimization
- 🎨 Cell Formatting - 14 predefined styles (bold, currency, %, highlights, borders) - WORKING!
- 📏 Column Width & Row Height - Customize column widths and row heights
- 📐 Formula Support - Write Excel formulas (=SUM, =AVERAGE, =IF, etc.)
- 🎯 Typed Values - Strong typing with Int, Float, Bool, DateTime, Formula
- 🔧 Memory Efficient - Handles 50+ columns with mixed data types
- ❌ Better Errors - Context-rich error messages with available sheets list
- 📊 Multi-format Support - Read XLSX, XLS, ODS formats
- 🔒 Type-safe - Leverage Rust's type system for safety
- 📝 Multi-sheet - Support multiple sheets in one workbook
- 🪟 Cross-Platform - Works perfectly on Windows, Linux, and macOS
- ✅ Production Ready - 50+ tests, CI/CD, zero unsafe code
📦 Installation
Add to your Cargo.toml:
[]
= "0.6"
Latest version: 0.6.2 - Upgraded zip 6.0, removed deprecated code, better memory optimization
🚀 Quick Start
Reading Excel Files (Streaming)
use ExcelReader;
Writing Excel Files (Streaming - v0.2.0)
use ExcelWriter;
Key Benefits:
- ✅ Constant ~80MB memory usage regardless of dataset size
- ✅ High throughput: 60K-70K rows/sec (UltraLowMemoryWorkbook fastest at 69.5K)
- ✅ Direct ZIP streaming - data written to disk immediately
- ✅ Full formatting support: bold, styles, column widths, row heights
Writing with Typed Values (Recommended)
For better Excel compatibility and performance, use typed values:
use ExcelWriter;
use CellValue;
Benefits of write_row_typed():
- ✅ Numbers are stored as numbers (not text)
- ✅ Booleans display as TRUE/FALSE
- ✅ Excel formulas work correctly
- ✅ Better type safety
- ✅ Excellent performance: 62.7K rows/sec (+2% faster than string-based)
Preserving Leading Zeros (Phone Numbers, IDs)
New in v0.6.1: Proper handling of string numbers with leading zeros!
Problem: Leading Zeros Lost
// ❌ WRONG: Auto number detection loses leading zeros
writer.write_row; // Displays as 90899 in Excel
Solution 1: Use write_row() (All Strings)
// ✅ CORRECT: write_row() treats ALL values as strings
writer.write_row;
// Displays: "090899", "00123", "ID-00456" ✅ Leading zeros preserved!
As of v0.6.1, write_row() no longer auto-detects numbers. All values are treated as strings.
Solution 2: Use write_row_typed() with Explicit Types
use CellValue;
// ✅ String type: preserves leading zeros
writer.write_row_typed?;
// ✅ Int type: actual number (no leading zero)
writer.write_row_typed?;
Solution 3: Use write_row_styled() for Full Control
use ;
writer.write_row_styled?;
Type Handling Summary:
| Method | String "090899" | Int 90899 | Use When |
|---|---|---|---|
write_row(&[&str]) |
"090899" ✅ | N/A | All data is text (IDs, codes) |
write_row_typed(CellValue) |
"090899" ✅ | 90899 (number) | Mixed types, explicit control |
write_row_styled() |
"090899" ✅ | 90899 (number) | Need formatting + type control |
Best Practice:
- Phone numbers, IDs, ZIP codes → Use
CellValue::Stringorwrite_row() - Amounts, quantities, calculations → Use
CellValue::IntorCellValue::Float
Writing Excel Formulas
use ExcelWriter;
use CellValue;
Supported Formulas:
- ✅ Basic arithmetic:
=A1+B1,=A1*B1,=A1/B1 - ✅ SUM, AVERAGE, COUNT, MIN, MAX
- ✅ Cell ranges:
=SUM(A1:A10) - ✅ Complex formulas:
=IF(A1>100, "High", "Low") - ✅ All standard Excel functions
Cell Formatting and Styling
New in v0.3.0: Apply 14 predefined cell styles including bold headers, number formats, highlights, and borders!
Bold Headers
use ExcelWriter;
let mut writer = new?;
// Write bold header
writer.write_header_bold?;
// Regular data rows
writer.write_row?;
writer.write_row?;
writer.save?;
Styled Cells
Apply different styles to individual cells:
use ;
use ExcelWriter;
let mut writer = new?;
writer.write_header_bold?;
// Mix different styles in one row
writer.write_row_styled?;
writer.write_row_styled?;
writer.save?;
Uniform Row Styling
Apply the same style to all cells in a row:
// All cells bold
writer.write_row_with_style?;
// All cells highlighted yellow
writer.write_row_with_style?;
Available Cell Styles
| Style | Format Code | Example | Use Case |
|---|---|---|---|
CellStyle::Default |
None | Plain text | Regular data |
CellStyle::HeaderBold |
Bold | Header | Column headers |
CellStyle::NumberInteger |
#,##0 | 1,234 | Whole numbers |
CellStyle::NumberDecimal |
#,##0.00 | 1,234.56 | Decimals |
CellStyle::NumberCurrency |
$#,##0.00 | $1,234.56 | Money amounts |
CellStyle::NumberPercentage |
0.00% | 95.00% | Percentages |
CellStyle::DateDefault |
MM/DD/YYYY | 01/15/2024 | Dates |
CellStyle::DateTimestamp |
MM/DD/YYYY HH:MM:SS | 01/15/2024 14:30:00 | Timestamps |
CellStyle::TextBold |
Bold | Bold text | Emphasis |
CellStyle::TextItalic |
Italic | Italic text | Notes |
CellStyle::HighlightYellow |
Yellow bg | 🟨 Text | Warnings |
CellStyle::HighlightGreen |
Green bg | 🟩 Text | Success |
CellStyle::HighlightRed |
Red bg | 🟥 Text | Errors |
CellStyle::BorderThin |
Thin borders | ▭ Text | Boundaries |
Complete Example
use ExcelWriter;
use ;
See also: Run cargo run --example cell_formatting to see all 14 styles in action!
Column Width and Row Height
New in v0.4.0: Customize column widths and row heights for better formatting!
Column Width
Set column widths before writing any rows:
use ExcelWriter;
let mut writer = new?;
// Set column widths BEFORE writing rows
writer.set_column_width?; // Column A = 25 units wide
writer.set_column_width?; // Column B = 12 units wide
writer.set_column_width?; // Column C = 15 units wide
// Now write rows
writer.write_header_bold?;
writer.write_row?;
writer.save?;
Important:
- ⚠️ Column widths must be set before writing any rows
- Default column width is 8.43 units
- One unit ≈ width of one character in default font
- Typical range: 8-50 units
Row Height
Set row height for the next row to be written:
use ExcelWriter;
let mut writer = new?;
// Set height for header row (taller)
writer.set_next_row_height?;
writer.write_header_bold?;
// Regular row (default height)
writer.write_row?;
// Set height for next row
writer.set_next_row_height?;
writer.write_row?;
writer.save?;
Important:
- Height is in points (1 point = 1/72 inch)
- Default row height is 15 points
- Typical range: 10-50 points
- Setting is consumed by next
write_row()call
Complete Example with Sizing
use ExcelWriter;
use ;
See also: Run cargo run --example column_width_row_height for a complete demonstration!
Direct FastWorkbook Usage (Maximum Performance)
For maximum performance, use FastWorkbook directly:
use FastWorkbook;
🧠 Hybrid SST Optimization (v0.5.0)
New in v0.5.0: Intelligent selective deduplication for optimal memory usage!
How It Works
The Hybrid Shared String Table (SST) strategy intelligently decides which strings to deduplicate:
// Automatic optimization - no code changes needed!
let mut workbook = new?;
workbook.add_worksheet?;
// Numbers → Inline as number type (no SST)
workbook.write_row?;
// Long strings (>50 chars) → Inline (usually unique)
workbook.write_row?;
// Short repeated strings → SST (efficient deduplication)
workbook.write_row?;
Memory Improvements
| Workbook Type | Before v0.5.0 | After v0.5.0 | Reduction |
|---|---|---|---|
| Simple (5 cols, 1M rows) | 49 MB | 18.8 MB | 62% |
| Medium (19 cols, 1M rows) | 125 MB | 15.4 MB | 88% |
| Complex (50 cols, 100K rows) | ~200 MB | 22.7 MB | 89% |
| Multi-workbook (4 × 100K rows) | 251 MB | 25.3 MB | 90% |
Strategy Details
The hybrid approach uses these rules:
- Numbers (
123,456.78) → Inline as<c t="n">(no SST) - Long strings (>50 chars) → Inline as
<c t="inlineStr">(usually unique) - SST Full (>100k unique) → New strings inline (graceful degradation)
- Short strings (≤50 chars) → SST for deduplication (efficient)
Performance Impact
ExcelWriter.write_row(): 16,250 rows/sec (baseline)
ExcelWriter.write_row_typed(): 19,642 rows/sec (+21%)
ExcelWriter.write_row_styled(): 18,581 rows/sec (+14%)
FastWorkbook (hybrid SST): 25,682 rows/sec (+58%) ⚡
Key Benefits:
- ✅ 89% less memory for complex workbooks
- ✅ 58% faster due to fewer SST lookups
- ✅ Handles 50+ columns with mixed data types
- ✅ Automatic - no API changes required
- ✅ Graceful degradation - caps at 100k unique strings
See also: HYBRID_SST_OPTIMIZATION.md for technical details
🗜️ Compression Level Configuration (v0.5.1)
New in v0.5.1: Control ZIP compression levels to balance speed vs file size!
Understanding Compression Levels
Excel files (.xlsx) are ZIP archives. ExcelStream lets you control the compression level:
| Level | Speed | File Size | Use Case | Recommended For |
|---|---|---|---|---|
| 0 | Fastest | Largest (no compression) | Debugging only | Testing |
| 1 | Very Fast ⚡ | ~2x larger | Fast exports | Development, testing |
| 3 | Fast | Balanced | Good compromise | CI/CD pipelines |
| 6 | Moderate | Smallest 📦 | Best compression | Production exports |
| 9 | Slowest | Smallest | Maximum compression | Archives, long-term storage |
Default: Level 6 (balanced performance and file size)
Setting Compression Level
Method 1: At Workbook Creation
use ExcelWriter;
// Create writer with fast compression (level 1)
let mut writer = with_compression?;
writer.write_header?;
writer.write_row?;
writer.save?;
Method 2: After Creation
use ExcelWriter;
let mut writer = new?;
// Change compression level
writer.set_compression_level; // Fast balanced compression
writer.write_header?;
writer.write_row?;
writer.save?;
Method 3: With UltraLowMemoryWorkbook
use UltraLowMemoryWorkbook;
let mut workbook = with_compression?;
workbook.add_worksheet?;
workbook.write_row?;
workbook.write_row?;
workbook.close?;
Method 4: Environment-Based Configuration
use ExcelWriter;
// Use fast compression for debug builds, production compression for release
let compression_level = if cfg! else ;
let mut writer = with_compression?;
writer.write_header?;
writer.write_row?;
writer.save?;
Real-World Performance (1M rows)
Tested with 1 million rows × 4 columns on production hardware:
| Configuration | Flush Interval | Buffer Size | Compression | Time | File Size | Memory |
|---|---|---|---|---|---|---|
| Aggressive | 100 | 256 KB | Level 1 | 3.93s ⚡ | 172 MB | <30 MB |
| Balanced | 500 | 512 KB | Level 3 | 5.03s | 110 MB | <30 MB |
| Default | 1000 | 1 MB | Level 6 | 7.37s | 88 MB 📦 | <30 MB |
| Conservative | 5000 | 2 MB | Level 6 | 8.00s | 88 MB | <30 MB |
Key Findings:
- Level 1 is ~2x faster but files are ~2x larger than level 6
- Level 3 provides a good balance between speed and size
- Memory usage is constant <30 MB regardless of compression level
- Production exports typically use level 6 for optimal file size
Complete Example: Configurable Compression
use ExcelWriter;
Recommendations
For Development & Testing:
let mut writer = with_compression?;
writer.set_flush_interval;
- ✅ Fast exports (2x speed improvement)
- ✅ Quick iteration cycles
- ⚠️ Larger files (not for production)
For CI/CD Pipelines:
let mut writer = with_compression?;
writer.set_flush_interval;
- ✅ Good balance of speed and size
- ✅ Reasonable export times
- ✅ Acceptable file sizes
For Production Exports:
let mut writer = with_compression?; // Default
writer.set_flush_interval;
- ✅ Smallest file size
- ✅ Best for network transfers
- ✅ Optimal for storage
For Archives:
let mut writer = with_compression?;
- ✅ Maximum compression
- ⚠️ Slower export times
- 📦 Best for long-term storage
See also: Run cargo run --example compression_level_config for a complete demonstration!
Memory-Constrained Writing (For Kubernetes Pods)
With v0.5.0+ and compression configuration (v0.5.1), memory usage is ultra-low:
use ExcelWriter;
Memory usage in v0.5.1:
- 10-30 MB peak with optimized settings (was 80-100 MB)
- 80-90% memory reduction vs v0.4.x
- Handles 50+ columns with mixed data types
- Suitable for Kubernetes pods with limited resources
- Automatic hybrid SST optimization
Configuration Presets:
| Preset | Flush | Buffer | Compression | Use Case | Memory Peak |
|---|---|---|---|---|---|
| Aggressive | 100 | 256 KB | Level 1 | <256 MB RAM pods | 10-15 MB |
| Balanced | 500 | 512 KB | Level 3 | <512 MB RAM pods | 15-20 MB |
| Default | 1000 | 1 MB | Level 6 | Standard pods | 20-30 MB |
| Conservative | 5000 | 2 MB | Level 6 | High-memory pods | 25-35 MB |
PostgreSQL Streaming Export (Production-Tested)
ExcelStream has been tested with real production databases. Example: 430,099 e-invoice records exported successfully.
use ExcelWriter;
use ;
Production Results (430K rows):
- Duration: 1m 34s (94.17 seconds)
- Throughput: 4,567 rows/sec
- File Size: 62.22 MB
- Memory Peak: <30 MB
- Columns: 25 mixed data types (int, float, text, dates)
Key Optimizations:
- ✅ Cursor-based streaming (no full table load)
- ✅ Small batch size (500 rows) for memory efficiency
- ✅ Proper NULL handling with
try_get().ok().flatten() - ✅ Fast compression (level 3) for balanced performance
- ✅ Frequent flushing (500 rows) to disk
See also: examples/postgres_streaming.rs for complete implementation
Multi-sheet workbook
use ExcelWriterBuilder;
📚 Examples
The examples/ directory contains detailed examples:
Basic Usage:
basic_read.rs- Basic Excel file readingbasic_write.rs- Basic Excel file writingstreaming_read.rs- Reading large files with streamingstreaming_write.rs- Writing large files with streaming
Performance Comparisons:
three_writers_comparison.rs- Compare all 3 writer types (recommended!)write_row_comparison.rs- String vs typed value writingwriter_comparison.rs- Standard vs fast writer comparisonfast_writer_test.rs- Fast writer performance benchmarks
Advanced Features:
memory_constrained_write.rs- Memory-limited writing with compression configauto_memory_config.rs- Auto memory configuration democompression_level_config.rs- Compression level configuration examplescsv_to_excel.rs- CSV to Excel conversionmulti_sheet.rs- Creating multi-sheet workbooks
PostgreSQL Integration:
postgres_to_excel.rs- Basic PostgreSQL exportpostgres_streaming.rs- Production-tested streaming export (430K rows)postgres_to_excel_advanced.rs- Advanced async with connection pooling
Running examples:
# Create sample data first
# Read Excel file
# Streaming with large files
# Performance comparisons (RECOMMENDED)
# Memory-constrained writing with compression
MEMORY_LIMIT_MB=512
# Compression level examples
# Multi-sheet workbooks
# PostgreSQL examples (requires database setup)
🔧 API Documentation
ExcelReader
open(path)- Open Excel file for readingsheet_names()- Get list of sheet namesrows(sheet_name)- Iterator for streaming row readingread_cell(sheet, row, col)- Read specific celldimensions(sheet_name)- Get sheet dimensions (rows, cols)
ExcelWriter (Streaming)
new(path)- Create new writer with default compression (level 6)with_compression(path, level)- Create with custom compression level (0-9)write_row(data)- Write row with stringswrite_row_typed(cells)- Write row with typed values (recommended)write_header(headers)- Write header rowwrite_header_bold(headers)- Write bold header rowwrite_row_styled(cells)- Write row with individual cell styleswrite_row_with_style(cells, style)- Write row with uniform styleadd_sheet(name)- Add new sheetset_flush_interval(rows)- Configure flush frequency (default: 1000)set_max_buffer_size(bytes)- Configure buffer size (default: 1MB)set_compression_level(level)- Set compression level (0-9, default: 6)compression_level()- Get current compression levelset_column_width(col, width)- Set column width (before writing rows)set_next_row_height(height)- Set height for next rowsave()- Save and finalize workbook
FastWorkbook (Direct Access)
new(path)- Create fast writer with default compression (level 6)with_compression(path, level)- Create with custom compression level (0-9)add_worksheet(name)- Add worksheetwrite_row(data)- Write row (optimized)set_flush_interval(rows)- Set flush frequencyset_max_buffer_size(bytes)- Set buffer limitset_compression_level(level)- Set compression level (0-9)compression_level()- Get current compression levelclose()- Finish and save file
Types
CellValue- Enum: Empty, String, Int, Float, Bool, DateTime, Error, FormulaRow- Row with index and cells vectorCell- Cell with position (row, col) and value
🎯 Use Cases
Processing Large Excel Files (100MB+)
// Streaming ensures only small portions are loaded into memory
let mut reader = open?;
let mut total = 0.0;
for row_result in reader.rows?
Exporting Database to Excel
let mut writer = new?;
writer.write_header?;
// Fetch from database and write directly
for record in database.query?
writer.save?;
Converting CSV to Excel
use File;
use ;
let csv = new;
let mut writer = new?;
for in csv.lines.enumerate
writer.save?;
⚡ Performance
Benchmarked with 1 million rows × 30 columns (mixed data types):
| Writer Method | Throughput | Memory Usage | Features |
|---|---|---|---|
| UltraLowMemoryWorkbook (direct) | 69,500 rows/sec ⚡ | ~80MB constant ✅ | FASTEST - Low-level API, max control |
| ExcelWriter.write_row_typed() | 62,700 rows/sec | ~80MB constant ✅ | Type-safe, best balance (+2% vs baseline) |
| ExcelWriter.write_row() | 61,400 rows/sec | ~80MB constant ✅ | Simple API, string-based (baseline) |
| ExcelWriter.write_row_styled() | 43,500 rows/sec | ~80MB constant ✅ | Cell formatting - 29% slower due to styling overhead |
Key Characteristics:
- ✅ High throughput - 43K-70K rows/sec depending on method
- ✅ Constant memory - stays at ~80MB regardless of dataset size
- ✅ True streaming - data written directly to disk via ZIP
- ✅ Predictable performance - no memory spikes or slowdowns
- ⚡ UltraLowMemoryWorkbook is FASTEST - Direct low-level access (+13% vs baseline)
- ⚠️ Styling has overhead - write_row_styled() is 29% slower but adds formatting
Recommendations
| Use Case | Recommended Method | Why |
|---|---|---|
| General use | write_row_typed() |
Best balance - Type-safe, fast (62.7K rows/sec, +2%) |
| Simple exports | write_row() |
Easy API, good performance (61.4K rows/sec) |
| Formatted reports | write_row_styled() |
Cell formatting - slower but worth it (43.5K rows/sec, -29%) |
| Maximum speed | UltraLowMemoryWorkbook |
FASTEST - Direct low-level access (69.5K rows/sec, +13%) |
📖 Documentation
- README.md (this file) - Complete guide with examples
- CONTRIBUTING.md - How to contribute
- CHANGELOG.md - Version history
- Examples in
/examplesdirectory - Doc tests in source code
🛠️ Development
Build
Test
Run examples
Benchmark
📋 Requirements
- Rust 1.70 or higher
- Dependencies:
calamine- Reading Excel fileszip- ZIP compression for writingthiserror- Error handling
🚀 Production Ready
- ✅ Battle-tested - Handles 1M+ row datasets with ease
- ✅ High performance - 43K-70K rows/sec depending on method (UltraLowMemoryWorkbook fastest!)
- ✅ Memory efficient - Constant ~80MB usage, perfect for K8s pods
- ✅ Reliable - 50+ comprehensive tests covering edge cases
- ✅ Safe - Zero unsafe code, full Rust memory safety
- ✅ Compatible - Excel, LibreOffice, Google Sheets
- ✅ Unicode support - Special characters, emojis, CJK
- ✅ CI/CD - Automated testing on every commit
🤝 Contributing
Contributions welcome! Please see CONTRIBUTING.md for guidelines.
Areas for Contribution:
- Cell formatting and styling (Phase 3)
- Conditional formatting
- Charts and images support
- More examples and documentation
- Performance optimizations
All contributions must:
- Pass
cargo fmt --check - Pass
cargo clippy -- -D warnings - Pass all tests
cargo test --all-features - Include tests for new features
📄 License
MIT License - see LICENSE file for details.
🙏 Credits
This library uses:
- calamine - Excel reader
- Custom FastWorkbook - High-performance streaming writer
📧 Contact
For questions or suggestions, please create an issue on GitHub.
Made with ❤️ and 🦀 by the Rust community