Skip to content
📖 Auto vs RustBook contents

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:

Accepting Command Line Arguments ​

To read command line arguments, use env.args():

Reading command line arguments with `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:

auto
let args = env.args().collect()

Note: env.args() will panic if any argument contains invalid Unicode. Use env.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:

Reading a file specified by command line arguments

Key points:

  • fn main() ! — the ! return type enables error propagation
  • fs.read_to_string(file_path).! — reads the file, propagating errors via .collect()
  • use env and use.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 ​

Modular minigrep with Config type and search function

This refactoring demonstrates several important patterns:

  1. type Config — groups configuration values into a single type
  2. Config.build() returns !Config — uses throw for error cases
  3. fn run(config Config) ! — separates program logic from setup
  4. fn main() ! — is minimal: build config, run, handle errors
  5. .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:

Environment variable for case-insensitive search

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:

console
$ IGNORE_CASE=1 automan run -- to poem.txt

Printing to Standard Error ​

Use eprint() for error messages (equivalent to Rust's eprintln!):

auto
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:

console
$ automan run -- query file.txt > results.txt

I/O Quick Reference ​

OperationAutoRust
CLI argumentsenv.args()std::env::args()
Read filefs.read_to_string(path).!std::fs::read_to_string(path)?
Environment varenv.var("KEY")std::env::var("KEY")
Check env setenv.var("KEY").is_ok()std::env::var("KEY").is_ok()
Stdoutprint()println!()
Stderreprint()eprintln!()
Exit with codesys.exit(code)std::process::exit(code)

Summary ​

This chapter demonstrated building a complete command line program in Auto:

  1. Accept command line arguments with env.args()
  2. Read files with use.rust std::fs and fs.read_to_string()
  3. Organize code with type Config and separate search/run functions
  4. Handle errors with !T return types and .collect() propagation
  5. Environment variables with env.var()
  6. 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.

Released under the MIT License.