Parallel Processing
For CPU-intensive tasks, you can use the rayon
crate for parallel processing:
use rayon::prelude::*;
use std::path::{Path, PathBuf};
use structopt::StructOpt;
use walkdir::WalkDir;
#[derive(StructOpt, Debug)]
struct Opt {
#[structopt(parse(from_os_str))]
directory: PathBuf,
#[structopt(short, long)]
pattern: String,
}
fn search_file(path: &Path, pattern: &str) -> Result<Vec<String>, std::io::Error> {
let content = std::fs::read_to_string(path)?;
let matches: Vec<String> = content
.lines()
.enumerate()
.filter(|(_, line)| line.contains(pattern))
.map(|(i, line)| format!("{}:{}: {}", path.display(), i + 1, line))
.collect();
Ok(matches)
}
fn main() -> Result<(), Box<dyn std::error::Error>> {
let opt = Opt::from_args();
// Find all files in the directory
let files: Vec<PathBuf> = WalkDir::new(&opt.directory)
.into_iter()
.filter_map(Result::ok)
.filter(|e| e.file_type().is_file())
.map(|e| e.path().to_owned())
.collect();
println!("Searching {} files for '{}'...", files.len(), opt.pattern);
// Search files in parallel
let results: Vec<String> = files
.par_iter()
.filter_map(|path| {
match search_file(path, &opt.pattern) {
Ok(matches) if !matches.is_empty() => Some(matches),
_ => None,
}
})
.flatten()
.collect();
// Print results
for line in results {
println!("{}", line);
}
Ok(())
}
This example implements a simple parallel grep-like tool that searches for a pattern in all files in a directory.
Best Practices for CLI Applications
Based on experience from large Rust projects, here are some best practices for building command-line applications:
1. Follow the Unix Philosophy
- Do one thing and do it well
- Process text streams as the universal interface
- Make composition easy
- Make output suitable for both humans and machines
2. Provide Good Documentation
- Include comprehensive help text
- Document all options and arguments
- Provide examples of common use cases
- Consider adding a man page for Unix-like systems
3. Be Consistent with Platform Conventions
- Use double dashes for long options (
--option
) - Use single dashes for short options (
-o
) - Follow platform-specific conventions for file paths, line endings, etc.
4. Handle Errors Gracefully
- Provide clear error messages
- Use appropriate exit codes
- Don’t crash on invalid input
- Log errors to stderr, not stdout
5. Respect the Environment
- Honor environment variables
- Use configuration files in standard locations
- Don’t modify files without permission
- Clean up temporary files
6. Provide Feedback
- Show progress for long-running operations
- Use colors and formatting judiciously
- Support both interactive and non-interactive modes
- Provide verbose and quiet options
Conclusion
Rust is an excellent choice for building command-line applications, offering a combination of performance, safety, and ergonomics that few other languages can match. With its rich ecosystem of libraries and tools, you can create sophisticated CLI applications that are both user-friendly and robust.
The key takeaways from this guide are:
- Use dedicated libraries like
clap
andstructopt
for argument parsing - Handle errors gracefully with
Result
,?
, and crates likeanyhow
andthiserror
- Provide good user experience with clear help text, progress indicators, and interactive input
- Test thoroughly to ensure your application works as expected
- Optimize for distribution to make your application accessible to users
By following these principles and leveraging Rust’s strengths, you can build command-line applications that are a joy to use and maintain. Whether you’re creating simple utilities or complex tools, Rust provides the foundation for building reliable, efficient CLI applications that stand the test of time.