Rust Study Session #11
Ch. 12 (part 1): Building a Command Line Program
2020.10.16 - Salvatore La Bua
I/O Project: Implementing grep
Organising code (Ch. 7)
Using vectors and strings (Ch. 8)
Handling errors (Ch. 9)
Using traits and lifetimes (Ch. 10)
Writing tests (Ch. 11)
Summary
Accepting Command Line Arguments
Reading a File
Refactoring
Command Line Arguments
Creating the project
$ cargo new minigrep
Created binary (application) `minigrep` project
$ cd minigrep
Command line call example
$ cargo run searchstring example-filename.txt
Reading the Argument Values
std::env::args is a function from the standard library that returns an iterator.
An iterator can be converted to a collection with the .collect() method.
use std::env;
fn main () {
let args : Vec <String > = env::args ().collect ();
println! ("{:?}" , args);
}
※ For invalid unicode, use std::env::args_os instead.
Reading the Argument Values
Running the program without arguments
$ cargo run
Compiling minigrep v0.1.0 (file:///projects/minigrep)
Finished dev [unoptimized + debuginfo] target(s) in 0.61s
Running `target/debug/minigrep`
["target/debug/minigrep" ]
Running the program with two arguments
$ cargo run needle haystack
Compiling minigrep v0.1.0 (file:///projects/minigrep)
Finished dev [unoptimized + debuginfo] target(s) in 1.57s
Running `target/debug/minigrep needle haystack`
["target/debug/minigrep" , "needle" , "haystack" ]
Saving the Argument Values in Variables
use std::env;
fn main () {
let args : Vec <String > = env::args ().collect ();
let query = &args[1 ];
let filename = &args[2 ];
println! ("Searching for {}" , query);
println! ("In file {}" , filename);
}
Running the program
$ cargo run test sample.txt
Compiling minigrep v0.1.0 (file:///projects/minigrep)
Finished dev [unoptimized + debuginfo] target(s) in 0.0s
Running `target/debug/minigrep test sample.txt`
Searching for test
In file sample.txt
Reading a File
Test file
I’m nobody! Who are you?
Are you nobody, too?
Then there’s a pair of us - don’t tell!
They’d banish us, you know.
How dreary to be somebody!
How public , like a frog
To tell your name the livelong day
To an admiring bog!
Reading a File
The code to read a file from disk
use std::env;
use std::fs;
fn main () {
let args : Vec <String > = env::args ().collect ();
let query = &args[1 ];
let filename = &args[2 ];
println! ("Searching for {}" , query);
println! ("In file {}" , filename);
let contents = fs::read_to_string (filename)
.expect ("Something went wrong reading the file" );
println! ("With text:\n{}" , contents);
}
Reading a file
Running the program
$ cargo run the poem.txt
Compiling minigrep v0.1.0 (file:///projects/minigrep)
Finished dev [unoptimized + debuginfo] target(s) in 0.0s
Running `target/debug/minigrep the poem.txt`
Searching for the
In file poem.txt
With text:
I’m nobody! Who are you?
Are you nobody, too?
Then there’s a pair of us - don’t tell!
They’d banish us, you know.
How dreary to be somebody!
How public, like a frog
To tell your name the livelong day
To an admiring bog!
Refactoring
Improve modularity
Separating functionalities
Grouping configuration variables
Error handling
Customise error messages
Grouping error handling code
Separating functionalities
When more functions are being added to the main program:
Split program into main.rs and lib.rs .
main.rs
Command line parsing.
Configuration set-up.
Calling a run function in lib.rs .
Handling errors if run returns an error.
lib.rs
Any of the program's logic.
Separating functionalities
Applying the separation
fn main () {
let args : Vec <String > = env::args ().collect ();
let (query, filename) = parse_config (&args);
}
fn parse_config (args: &[String ]) -> (&str , &str ) {
let query = &args[1 ];
let filename = &args[2 ];
(query, filename)
}
Grouping configuration variables
Creating a structure for the configuration
fn main () {
let args : Vec <String > = env::args ().collect ();
let config = parse_config (&args);
println! ("Searching for {}" , config.query);
println! ("In file {}" , config.filename);
let contents = fs::read_to_string (config.filename)
.expect ("Something went wrong reading the file" );
}
struct Config {
query: String ,
filename: String ,
}
fn parse_config (args: &[String ]) -> Config {
let query = args[1 ].clone ();
let filename = args[2 ].clone ();
Config { query, filename }
}
Grouping configuration variables
Creating a constructor for Config
fn main () {
let args : Vec <String > = env::args ().collect ();
let config = Config::new (&args);
}
impl Config {
fn new (args: &[String ]) -> Config {
let query = args[1 ].clone ();
let filename = args[2 ].clone ();
Config { query, filename }
}
}
Error handling
Customise error messages
$ cargo run
Compiling minigrep v0.1.0 (file:///projects/minigrep)
Finished dev [unoptimized + debuginfo] target(s) in 0.0s
Running `target/debug/minigrep`
thread 'main' panicked at 'index out of bounds: the len is 1 but the index is 1' , src/main.rs:27:21
note : run with `RUST_BACKTRACE=1` environment variable to display a backtrace.
Error handling
Customise error messages
struct Config {
query: String ,
filename: String ,
}
impl Config {
fn new (args: &[String ]) -> Config {
if args.len () < 3 {
panic! ("not enough arguments" );
}
let query = args[1 ].clone ();
let filename = args[2 ].clone ();
Config { query, filename }
}
}
Error handling
Customise error messages
$ cargo run
Compiling minigrep v0.1.0 (file:///projects/minigrep)
Finished dev [unoptimized + debuginfo] target(s) in 0.0s
Running `target/debug/minigrep`
thread 'main' panicked at 'not enough arguments' , src/main.rs:26:13
note : run with `RUST_BACKTRACE=1` environment variable to display a backtrace.
Error handling
Returning a Result
impl Config {
fn new (args: &[String ]) -> Result <Config, &'static str > {
if args.len () < 3 {
return Err ("not enough arguments" );
}
let query = args[1 ].clone ();
let filename = args[2 ].clone ();
Ok (Config { query, filename })
}
}
Error handling
Calling Config::new
use std::process;
fn main () {
let args : Vec <String > = env::args ().collect ();
let config = Config::new (&args).unwrap_or_else (|err| {
println! ("Problem parsing arguments: {}" , err);
process::exit (1 );
});
$ cargo run
Compiling minigrep v0.1.0 (file:///projects/minigrep)
Finished dev [unoptimized + debuginfo] target(s) in 0.48s
Running `target/debug/minigrep`
Problem parsing arguments: not enough arguments
Separating functionalities
Extracting logic from main
fn main () {
println! ("Searching for {}" , config.query);
println! ("In file {}" , config.filename);
run (config);
}
fn run (config: Config) {
let contents = fs::read_to_string (config.filename)
.expect ("Something went wrong reading the file" );
println! ("With text:\n{}" , contents);
}
Error handling
Returning Errors from the run Function
use std::error::Error;
fn run (config: Config) -> Result <(), Box <dyn Error>> {
let contents = fs::read_to_string (config.filename)?;
println! ("With text:\n{}" , contents);
Ok (())
}
Error handling
Returning Errors from the run Function
$ cargo run the poem.txt
Compiling minigrep v0.1.0 (file:///projects/minigrep)
warning: unused `std::result::Result` that must be used
--> src/main.rs:19:5
|
19 | run(config);
| ^^^^^^^^^^^^
|
= note : `
= note : this `Result` may be an `Err` variant, which should be handled
Finished dev [unoptimized + debuginfo] target(s) in 0.71s
Running `target/debug/minigrep the poem.txt`
Searching for the
In file poem.txt
With text:
I’m nobody! Who are you?
Are you nobody, too?
Then there’s a pair of us - don’t tell!
They’d banish us, you know.
How dreary to be somebody!
How public, like a frog
To tell your name the livelong day
To an admiring bog!
Error handling
Errors returned from run to main
fn main () {
println! ("Searching for {}" , config.query);
println! ("In file {}" , config.filename);
if let Err (e) = run (config) {
println! ("Application error: {}" , e);
process::exit (1 );
}
}
Separating functionalities
Splitting code into a library crate
Move the code that is not in the main function to lib.rs :
The relevant use statements
The definition of Config
The Config::new function definition
The run function definition
Separating functionalities
The library crate src/lib.rs
use std::error::Error;
use std::fs;
pub struct Config {
pub query: String ,
pub filename: String ,
}
impl Config {
pub fn new (args: &[String ]) -> Result <Config, &'static str > {
}
}
pub fn run (config: Config) -> Result <(), Box <dyn Error>> {
}
Separating functionalities
Importing the library crate into main.rs
use std::env;
use std::process;
use minigrep::Config;
fn main () {
if let Err (e) = minigrep::run (config) {
}
}
□
References
From The Rust Programming Language book:
Ch. 12: An I/O Project:
Building a Command Line Program [EN] [JP]
Resume presentation
Rust Study Session #11 Ch. 12 (part 1): Building a Command Line Program 2020.10.16 - Salvatore La Bua
{"metaMigratedAt":"2023-06-15T13:56:34.605Z","metaMigratedFrom":"YAML","title":"Rust Ch.12 (part 1) - Building a Command Line Program","breaks":true,"description":"Rust Ch.12 (part 1) - Building a Command Line Program","slideOptions":"{\"theme\":\"black\",\"slideNumber\":\"c/t\",\"center\":false,\"transition\":\"fade\",\"keyboard\":true}","contributors":"[{\"id\":\"c5f0d40d-be35-4660-a8b4-7736feeb9327\",\"add\":26788,\"del\":13284}]"}