An I/O Project: Building a Command Line Program
This chapter is a recap of the many skills you've learned so far and an exploration of standard library features for I/O. We'll build a command line tool — our own version of grep — to practice concepts from previous chapters.
Our minigrep project will combine:
- Organizing code with
typeandext(Chapter 7) - Using
List<T>andString(Chapter 8) - Handling errors with
!Tand.collect()(Chapter 9) - Using specs (Chapter 10)
- Writing tests (Chapter 11)
Accepting Command Line Arguments
To read command line arguments, use env.args():
env.args() returns an iterator of the command line arguments. The first argument is always the program name itself. To collect them into a List:
let args = env.args().collect()Note:
env.args()will panic if any argument contains invalid Unicode. Useenv.args_os()for arguments that may contain invalid Unicode.
Reading a File
Use fs.read_to_string() from Rust's standard library via use.rust:
Key points:
fn main() !— the!return type enables error propagationfs.read_to_string(file_path).!— reads the file, propagating errors via.collect()use envanduse.rust std::fs— import environment and filesystem modules
Refactoring for Modularity
As the program grows, we extract logic into separate functions and a Config type. This follows the Rust convention of separating concerns between binary (main.rs) and library (lib.rs) code.
Extracting a Config Type
This refactoring demonstrates several important patterns:
type Config— groups configuration values into a single typeConfig.build()returns!Config— usesthrowfor error casesfn run(config Config) !— separates program logic from setupfn main() !— is minimal: build config, run, handle errors.collect()on!T— equivalent to Rust's?operator
Environment Variables
Environment variables let users configure behavior without changing command line arguments. Use env.var("VAR_NAME") which returns !String:
env.var("IGNORE_CASE") returns !String. The .is_ok() method checks if the environment variable is set (regardless of its value), returning a bool.
Run with the environment variable set:
$ IGNORE_CASE=1 automan run -- to poem.txtPrinting to Standard Error
Use eprint() for error messages (equivalent to Rust's eprintln!):
eprint("Error: file not found")This sends output to stderr instead of stdout, so users can redirect successful output to a file while still seeing error messages on screen:
$ automan run -- query file.txt > results.txtI/O Quick Reference
| Operation | Auto | Rust |
|---|---|---|
| CLI arguments | env.args() | std::env::args() |
| Read file | fs.read_to_string(path).! | std::fs::read_to_string(path)? |
| Environment var | env.var("KEY") | std::env::var("KEY") |
| Check env set | env.var("KEY").is_ok() | std::env::var("KEY").is_ok() |
| Stdout | print() | println!() |
| Stderr | eprint() | eprintln!() |
| Exit with code | sys.exit(code) | std::process::exit(code) |
Summary
This chapter demonstrated building a complete command line program in Auto:
- Accept command line arguments with
env.args() - Read files with
use.rust std::fsandfs.read_to_string() - Organize code with
type Configand separatesearch/runfunctions - Handle errors with
!Treturn types and.collect()propagation - Environment variables with
env.var() - Write to stderr with
eprint()
The project combined concepts from every previous chapter: types, extensions, collections, error handling, specs, generics, and testing.
In the next chapter, we'll explore functional language features: closures and iterators.