<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}]"}