Intro
This project culminates the previous 11 chapters with a simple remake of grep.
Reading CMD Args
Done using the env module from the standard library.
It has a function for the arguments passed to a program, which we bundle into a vector using .collect iterator.
Note
- The
args()function doesn’t accept invalid Unicode characters and will panic.- Collecting using the
collectiterator always requires annotation, otherwise Rust won’t be able to determine the type of collection wanted using inference alone…
Just like with C, the first argument passed to a program is its own name, the arguments follow after. Meaning args() will always have at least one element when collected.
Organizing Logic Concerns in Binary Projects
The convention is to split the program into main.rs and lib.rs.
The guide lays out the responsibilities of the main function to be the following:
- Getting the args and parsing different values
- Setting up any other configurations needed
- Calling a function that runs the logic flow
- Handling errors
The reason for this breakdown, besides cleaner code that’s more readable and maintainable is the following:
“This pattern is about separating concerns: main.rs handles running the program and lib.rs handles all the logic of the task at hand. Because you can’t test the
mainfunction directly, this structure lets you test all of your program’s logic by moving it out of themainfunction.”
Cloning
The clone method fixes many issues related to ownership because it does as the name implies.
However, it’s not recommended due to its inefficiency. The method works by making a full copy of the data in the heap, not just a reference…
Unwrap_or_else
This closure, does what the match expression to unpack a Result does, it receives the message from the Err and allows you to handle that case only, if Ok gets assigned.
let current_config: Config = Config::new(&args).unwrap_or_else(|err| {
eprintln!("Problem parsing arguments: {}", err);
process::exit(1);
});Setting up Environment variables
The example project aims to save a user preference, the option to have case-insensitive search, instead of implementing as another command line arg that the user needs to specify each time.
Creating new data when changing the casing of a
strUsing methods like
.to_lowercaseand.to_uppercasecreates newStringdata instead of mutating, that’s because it needs to fecth all the letters that aren’t already in the desired case.
The option is added to the Config struct in main.rs
Environment variables can be checked using the the env module from the standard library.
The env::var checks if a value has been set for a given environment variable.
Example:
let case_sensitive = env::var("IGNORE_CASE").is_ok();It returns a Result with either the value in Ok or an Err if not set, and the is_ok() method does that check for us and returns a bool.
To set the environment variable from our terminal’s shell:
$ IGNORE_CASE=1 cargo run -- to poem.txt“Some programs allow arguments and environment variables for the same configuration. In those cases, the programs decide that one or the other takes precedence.”
Redirecting Errors to Standard Error
Most terminals have two standard outputs, out and error.
Stdout is often for the general info the program conveys and Stderr is for error messages.
To observe what our program outputs to Stdout, we can direct the output to a file from our shell like so: cargo run > output.txt
Command Line Program Output Behaviors
The expected convention is that error messages are sent to the standard error stream, which means they still show on screen even when the program’s standard output stream is redirected or piped to another.
The eprintln! macro for Errors
The eprintln! macro prints the to standard error instead of output, so we can refactor our code to use it instead of println!.
Now errors are printed on screen and the output.txt file sees nothing, which is expected.
Trying again with valid arguments but still redirect:
$ cargo run -- to poem.txt > output.txtNow the results from the program are redirected to the output file.