Rust Project: File Compression CLI
Welcome to the first installment of the 50 Rust Projects series. In this foundational project, we build a command-line interface (CLI) utility in Rust that takes any target file, compresses it using the Gzip algorithm via an external crate, and outputs performance metrics such as processing time and compression ratio.
1. Why Do We Need This? (The Problem & Context)
When building systems engineering competencies—especially in domains like high-performance backend services, decentralized networks (Solana, Polkadot, NEAR), or low-latency tooling—efficiency is non-negotiable.
- The Status Quo: Developers often rely on high-level languages (like JavaScript/Node.js with
npm) or shell scripts for basic file operations. While fast to prototype, they may lack granular memory safety and raw CPU performance. - The Rust Advantage: Rust delivers C/C++ level performance with compile-time memory guarantees and a powerful package registry (
Cargo). Building a file compressor exposes developers to command-line arguments, buffered I/O, external crate management, and error handling.
2. Core Mental Model & Analogy
Real-Life Analogy: The Automated Mailroom Clerk Imagine an office mailroom clerk standing at a desk.
- CLI Arguments: The clerk expects exact instructions handed on a clipboard: Source file (what to pack) and Target file (where to put the packed box). If instructions are missing or incorrect, the clerk halts and explains the correct procedure.
- Buffered Reading (
BufReader): Instead of reading an entire heavy filing cabinet in one exhausting glance, the clerk reads documents in manageable chunks using a clipboard tray.- The Encoder (
GzEncoder): The clerk passes those documents through an industrial shrink-wrapping machine, compressing the volume before sealing it into the final destination envelope.
3. Initializing the Project with Cargo
Rust projects are managed via Cargo, Rust's build system and package manager.
Setting Up Workspace Directories
Open your terminal (in this case, a Windows environment running WSL/Ubuntu) and create a new binary crate:
# Navigate to your designated projects directory
cd ~/rust_projects
# Create a new binary project named "compress_youtube"
cargo new compress_youtube
# Change directory into the newly created project folder
cd compress_youtube
# Open the project in your code editor (e.g., Visual Studio Code)
code .
Understanding the Generated Structure
Upon running cargo new, Cargo automatically sets up the following layout:
4. Managing Dependencies: The flate2 Crate
Unlike Node.js where packages are installed globally or locally via npm install <package> before writing code, Rust handles dependencies declaratively inside Cargo.toml.
Adding flate2 to Cargo.toml
To compress files using Gzip, we use the industry-standard flate2 crate. Open your Cargo.toml file and add flate2 under dependencies:
[dependencies]
flate2 = "1.0"
(Note: You do not need to manually run an installation command like cargo install for project dependencies; Cargo resolves and compiles them automatically when building or running the project.)
5. Designing the CLI Workflow
Our CLI tool must accomplish the following execution steps sequentially:
- Validate user input arguments (expecting exactly 3 arguments: executable path, source file, target file).
- Open the source file safely using a buffered reader for optimal I/O performance.
- Create the destination target file.
- Initialize a Gzip compression encoder with default compression settings.
- Stream bytes from the input through the encoder into the output file using Rust's
io::copy. - Capture performance telemetry (elapsed time and file byte lengths).
6. Complete Code Implementation (src/main.rs)
Below is the complete, production-ready source code for our file compression utility.
use flate2::write::GzEncoder;
use flate2::Compression;
use std::env;
use std::fs::{self, File};
use std::io::{self, BufReader, copy};
use std::time::Instant;
fn main() {
// Collect command-line arguments into a vector of Strings
let args: Vec<String> = env::args().collect();
// Validate argument length: expects [executable_path, source_file, target_file]
if args.len() != 3 {
eprintln!("Usage: target <source> <target>");
return;
}
let source_path = &args[1];
let target_path = &args[2];
// Open the source file inside a buffered reader
let input_file = File::open(source_path).unwrap();
let mut reader = BufReader::new(input_file);
// Create the destination output file
let output_file = File::create(target_path).unwrap();
// Initialize the Gzip encoder with default compression level
let mut encoder = GzEncoder::new(output_file, Compression::default());
// Start timer for telemetry
let start = Instant::now();
// Copy bytes from the buffered reader through the encoder
copy(&mut reader, &mut encoder).unwrap();
// Finalize the encoding process and retrieve the underlying output file
let mut output = encoder.finish().unwrap();
// Retrieve metadata for telemetry reporting
let source_metadata = fs::metadata(source_path).unwrap();
let target_metadata = output.metadata().unwrap();
println!("Source length: {} bytes", source_metadata.len());
println!("Target length: {} bytes", target_metadata.len());
println!("Elapsed time: {:?}", start.elapsed());
}
7. Compilation & The Rust Compiler Advantage
One of Rust’s greatest strengths is its remarkably intelligent compiler (rustc). When beginners or experienced developers make syntax or naming errors, Rust does not just throw cryptic stack traces—it offers precise, human-readable diagnostic assistance.
Common Pitfalls Handled by the Compiler:
- Case Sensitivity: In
flate2, writingGzEncoderwith a capitalZinstead of lowercasegz(GzEncoder) or mixing up module paths will immediately trigger a compiler suggestion. - Missing Semicolons & Macros: Forgetting an exclamation mark on printing macros (
printlnvsprintln!) or missing closing parentheses on.unwrap()calls are caught instantly duringcargo run.
8. Execution and Real-World Testing
To test our compression utility, we use a moderately sized sample file (such as a 2.3 MB PDF document named book.pdf) to ensure a noticeable difference between uncompressed and compressed byte sizes.
Running the Tool
Execute the program via Cargo, supplying the source file and desired compressed output filename:
cargo run -- book.pdf compressed_file.gz
Expected Output Console Log:
Compiling compress_youtube v0.1.0 (/path/to/compress_youtube)
Finished dev [unoptimized + debuginfo] target(s) in 0.82s
Running `target/debug/compress_youtube book.pdf compressed_file.gz`
Source length: 2359296 bytes
Target length: 1845120 bytes
Elapsed time: 96.3ms
Verifying Files via Explorer (WSL / Ubuntu on Windows)
If working within a Windows Subsystem for Linux (WSL) environment, you can open your current directory graphically to inspect the file sizes side-by-side:
explorer.exe .
9. Summary and Key Takeaways
- Cargo Ecosystem: Cargo simplifies project creation, structure management, and external crate integration (
flate2) viaCargo.toml. - Command-Line Arguments:
std::env::args()allows robust retrieval and validation of CLI inputs (sourceandtargetpaths). - Buffered I/O Performance: Combining
BufReaderwithstd::io::copyensures efficient memory management and stream processing during file reads. - Gzip Compression:
GzEncodercombined withCompression::default()provides an effortless API for file compression in Rust. - Telemetry Tracking:
std::time::Instantallows developers to measure exact execution durations for performance-critical tasks. - The Rust Compiler as a Mentor: Rust's compiler diagnostics provide clear, actionable feedback, helping developers catch and fix bugs at compile-time rather than in production.
- Avoid Over-Reliance on Autocomplete: Building code with an understanding of raw syntax prevents dependency on IDE plugins and prepares developers for technical interviews and constrained environments.