<style> /* basic design */ .reveal h1, .reveal h2, .reveal h3, .reveal h4, .reveal h5, .reveal h6, .reveal section, .reveal table, .reveal li, .reveal blockquote, .reveal th, .reveal td, .reveal p { font-family: 'Meiryo UI', 'Source Sans Pro', Helvetica, sans-serif, 'Helvetica Neue', 'Helvetica', 'Arial', 'Hiragino Sans', 'ヒラギノ角ゴシック', YuGothic, 'Yu Gothic'; text-align: left; line-height: 1.5; letter-spacing: normal; text-shadow: none; word-wrap: break-word; color: #AAA; } .reveal h1, .reveal h2, .reveal h3, .reveal h4, .reveal h5, .reveal h6 {font-weight: bold;} .reveal h1, .reveal h2, .reveal h3 {color: #2980b9;} .reveal th {background: #DDD;} .reveal section img {background:none; border:none; box-shadow:none; max-width: 95%; max-height: 95%;} .reveal blockquote {width: 90%; padding: 0.5vw 3.0vw;} .reveal table {margin: 1.0vw auto;} .reveal code {line-height: 1.2;} .reveal p, .reveal li {padding: 0vw; margin: 0vw;} .reveal .box {margin: -0.5vw 1.5vw 2.0vw -1.5vw; padding: 0.5vw 1.5vw 0.5vw 1.5vw; background: #EEE; border-radius: 1.5vw;} /* table design */ .reveal table {background: #f5f5f5;} .reveal th {background: #444; color: #fff;} .reveal td {position: relative; transition: all 300ms;} .reveal tbody:hover td { color: transparent; text-shadow: 0 0 3px #AAA;} .reveal tbody:hover tr:hover td {color: #444; text-shadow: 0 1px 0 #CCC;} /* blockquote design */ .reveal blockquote { width: 90%; padding: 0.5vw 0 0.5vw 6.0vw; font-style: italic; background: #f5f5f5; } .reveal blockquote:before{ position: absolute; top: 0.1vw; left: 1vw; content: "\f10d"; font-family: FontAwesome; color: #2980b9; font-size: 3.0vw; } /* font size */ .reveal h1 {font-size: 4.0vw;} .reveal h2 {font-size: 3.5vw;} .reveal h3 {font-size: 2.8vw;} .reveal h4 {font-size: 2.6vw;} .reveal h5 {font-size: 2.4vw;} .reveal h6 {font-size: 2.2vw;} .reveal section, .reveal table, .reveal li, .reveal blockquote, .reveal th, .reveal td, .reveal p {font-size: 2.0vw;} .reveal code {font-size: 1.0vw;} /* new color */ .red {color: #EE6557;} .blue {color: #16A6B6;} /* split slide */ #right {left: -18.33%; text-align: left; float: left; width: 50%; z-index: -10;} #left {left: 31.25%; text-align: left; float: left; width: 50%; z-index: -10;} </style> <style> /* specific design */ .reveal h2 { padding: 0 1.5vw; margin: 0.0vw 0 2.0vw -2.0vw; border-left: solid 1.0vw #2980b9; border-bottom: solid 0.6vw #d7d7d7; } </style> <!-- --------------------------------------------------------------------------------------- --> # 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 ```bash= $ cargo new minigrep Created binary (application) `minigrep` project $ cd minigrep ``` #### Command line call example ```bash= $ 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. ```rust= use std::env; // a good practice is to import the parent module 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 ```bash= $ 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 ```bash= $ 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 ```rust= 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 ```bash= $ 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 ```rust= use std::env; use std::fs; fn main() { // --snip-- 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 ```bash= $ 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 ```rust= fn main() { let args: Vec<String> = env::args().collect(); let (query, filename) = parse_config(&args); // --snip-- } 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 ```rust= 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"); // --snip-- } 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 ```rust= fn main() { let args: Vec<String> = env::args().collect(); let config = Config::new(&args); // --snip-- } // --snip-- 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 ```bash= $ 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 ```rust= 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 ```bash= $ 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 ```rust= 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 ```rust= 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); }); // --snip-- ``` ```bash= $ 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 ```rust= fn main() { // --snip-- 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); } // --snip-- ``` --- ### Error handling #### Returning Errors from the run Function ```rust= use std::error::Error; // --snip-- 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 ```bash= $ 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 : `#[warn(unused_must_use)]` on by default = 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 ```rust= fn main() { // --snip-- 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* ```rust= // Relevant use statements use std::error::Error; use std::fs; // Definition of Config pub struct Config { pub query: String, pub filename: String, } // Config::new function definition impl Config { pub fn new(args: &[String]) -> Result<Config, &'static str> { // --snip-- } } // run function definition pub fn run(config: Config) -> Result<(), Box<dyn Error>> { // --snip-- } ``` --- ### Separating functionalities #### Importing the library crate into main.rs ```rust= //src/main.rs use std::env; use std::process; use minigrep::Config; fn main() { // --snip-- if let Err(e) = minigrep::run(config) { // --snip-- } } ``` <div style="text-align: right">□</div> --- ## References From __The Rust Programming Language__ book: - Ch. 12: An I/O Project: Building a Command Line Program [[EN]](https://doc.rust-lang.org/book/ch12-00-an-io-project.html) [[JP]](https://doc.rust-jp.rs/book-ja/ch12-00-an-io-project.html) ---
{"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}]"}
    449 views