Project Setup
First, let's set up our projects. We'll create a simple CLI tool that counts lines, words, and characters in a file (like wc
).
CLI Application Setup
Python
# Python: Using Click for CLI
# File: wc.py
import click
import sys
from pathlib import Path
@click.command()
@click.argument('file', type=click.Path(exists=True))
@click.option('--lines/--no-lines', '-l', default=True, help='Count lines')
@click.option('--words/--no-words', '-w', default=True, help='Count words')
@click.option('--chars/--no-chars', '-c', default=False, help='Count characters')
def wc(file, lines, words, chars):
"""A simple word count program."""
try:
content = Path(file).read_text()
counts = {}
if lines:
counts['lines'] = len(content.splitlines())
if words:
counts['words'] = len(content.split())
if chars:
counts['chars'] = len(content)
click.echo(f"{file}")
for name, count in counts.items():
click.echo(f" {name}: {count}")
except Exception as e:
click.echo(f"Error: {e}", err=True)
sys.exit(1)
if __name__ == '__main__':
wc()
Rust
// Rust: Using clap for CLI
// File: src/main.rs
use clap::Parser;
use std::fs;
use std::path::PathBuf;
/// A simple word count program
#[derive(Parser, Debug)]
#[command(author, version, about, long_about = None)]
struct Args {
/// Input file
file: PathBuf,
/// Count lines
#[arg(short, long, default_value_t = true)]
lines: bool,
/// Count words
#[arg(short, long, default_value_t = true)]
words: bool,
/// Count characters
#[arg(short, long, default_value_t = false)]
chars: bool,
}
fn main() -> Result<(), Box<dyn std::error::Error>> {
let args = Args::parse();
let content = fs::read_to_string(&args.file)?;
let mut counts = std::collections::HashMap::new();
if args.lines {
counts.insert("lines", content.lines().count());
}
if args.words {
counts.insert("words", content.split_whitespace().count());
}
if args.chars {
counts.insert("chars", content.chars().count());
}
println!("{}", args.file.display());
for (name, count) in counts {
println!(" {}: {}", name, count);
}
Ok(())
}
Dependencies
Both languages use package/dependency management, but they work differently:
Dependency Management
Python
# Python: requirements.txt or pyproject.toml
# Using pyproject.toml (modern approach)
[build-system]
requires = ["setuptools>=42"]
build-backend = "setuptools.build_meta"
[project]
name = "py-wc"
version = "0.1.0"
description = "A word count program in Python"
requires-python = ">=3.8"
dependencies = [
"click>=8.0.0",
]
[project.scripts]
py-wc = "wc:wc"
Rust
// Rust: Cargo.toml
[package]
name = "rust-wc"
version = "0.1.0"
edition = "2021"
description = "A word count program in Rust"
[dependencies]
clap = { version = "4.0", features = ["derive"] }
Building and Running
Let's see how to build and run our applications:
Building and Running
Python
# Install in development mode
$ pip install -e .
# Run the script
$ py-wc --help
Usage: py-wc [OPTIONS] FILE
A simple word count program.
Arguments:
FILE [required]
Options:
-l, --lines / --no-lines Count lines [default: True]
-w, --words / --no-words Count words [default: True]
-c, --chars / --no-chars Count characters [default: False]
--help Show this message and exit.
Rust
# Build in debug mode (fast compile, slower runtime)
$ cargo build
# Build in release mode (slower compile, faster runtime)
$ cargo build --release
# Run directly with Cargo
$ cargo run -- --help
A simple word count program
Usage: rust-wc [OPTIONS] <FILE>
Arguments:
<FILE> Input file
Options:
-l, --lines Count lines [default: true]
-w, --words Count words [default: true]
-c, --chars Count characters [default: false]
-h, --help Print help
-V, --version Print version
Error Handling
Both languages handle errors, but Rust's approach is more explicit:
Error Handling
Python
# Python: Exceptions
try:
with open("nonexistent.txt") as f:
content = f.read()
except FileNotFoundError as e:
print(f"Error: {e}", file=sys.stderr)
sys.exit(1)
except Exception as e:
print(f"Unexpected error: {e}", file=sys.stderr)
sys.exit(1)
Rust
// Rust: Result type
use std::fs::File;
use std::io::Read;
fn read_file(path: &str) -> Result<String, Box<dyn std::error::Error>> {
let mut file = File::open(path)?;
let mut content = String::new();
file.read_to_string(&mut content)?;
Ok(content)
}
// Usage:
match read_file("nonexistent.txt") {
Ok(content) => println!("File content: {}", content),
Err(e) => eprintln!("Error reading file: {}", e),
}
Testing
Both languages have built-in testing support:
Testing
Python
# test_wc.py
import pytest
from wc import wc
from click.testing import CliRunner
def test_wc_lines():
runner = CliRunner()
result = runner.invoke(wc, ["--no-words", "--no-chars", "test.txt"])
assert "lines: 10" in result.output
assert result.exit_code == 0
Rust
// tests/wc_test.rs
#[test]
fn test_count_lines() {
let content = "Hello
world
";
let args = Args {
file: "test.txt".into(),
lines: true,
words: false,
chars: false,
};
// Test logic here
assert_eq!(count_lines(&content), 2);
}
// In Cargo.toml:
// [[test]]
// name = "wc_test"
// path = "tests/wc_test.rs"
Packaging and Distribution
Packaging for distribution works differently in both ecosystems:
Packaging and Distribution
Python
# Python: Using setuptools and PyPI
# Build distribution
$ python -m build
# Upload to PyPI
$ twine upload dist/*
# Install from PyPI
$ pip install py-wc
Rust
// Rust: Using Cargo and crates.io
# Build for release
$ cargo build --release
# The binary is in target/release/rust-wc
# Publish to crates.io
$ cargo publish
# Install globally
$ cargo install rust-wc
Performance Comparison
Performance Note
Rust's compiled nature gives it a significant performance advantage:
- Startup time: Rust is typically 10-100x faster than Python
- Memory usage: Rust uses about half the memory of Python
- CPU usage: Rust is often 2-10x faster for CPU-bound tasks
- Binary size: Rust produces standalone binaries (larger but self-contained)
Key Takeaways
- Development speed: Python is often quicker for prototyping
- Performance: Rust provides better performance and lower resource usage
- Error handling: Rust's explicit error handling catches more issues at compile time
- Distribution: Rust produces standalone binaries, Python requires an interpreter
- Learning curve: Rust has a steeper learning curve but offers more control
When to Choose Which?
Choose Python When:
- Rapid prototyping is needed
- Performance is not critical
- You need extensive data science libraries
- Team is more familiar with Python
Choose Rust When:
- Performance is critical
- Memory safety is important
- You want to avoid runtime errors
- Building system-level tools