# Organizing Code, Modularity and Error Handling It is bad that everything is in main. `expect` is not very good, panic and bad default error message ## Separation of concerns Create a `lib.rs` and send logic when it gets too big to be readable in main. Main should only: - Call parsing logic with argument mvalues - Setup a configuration - Call a `run` function from lib.rs - Handle error returned by `run` if needed `Main` runs the program, but `lib` does the logic ### Step 1 - Parse in a different function ```rust // Function receives a borrowed array/vector of String // Returns a tuple of 2 string slices which are the arguments fn parse_args(args: &[String]) -> (&str, &str) { // Will panic if no arguments are passed let query = args[1].clone(); let filename = args[2].clone(); (query, filename) } ``` ### Step 2 - Declare Intent with the variables ```rust // Use specific struct to demark intent on the usage of arguments struct Config { query: String, filename: String, } // Returns a Config struct, demarcating intent of usage of the extracted arguments fn parse_args(args: &[String]) -> Config { // Will panic if no arguments are passed let query = args[1].clone(); let filename = args[2].clone(); Config { query, filename} } ``` Why clone? Because of simplicity, we could do: ```rust struct Config<'a> { query: &'a str, filename: &'a str, } fn parse_args(args: &[String]) -> Config { // Will panic if no arguments are passed let query = &args[1]; let filename = &args[2]; Config { query, filename} } ``` This would be correct, return `Config` now bound by the lifetime of the `args` passed, and internal borrows are bound by the lifetime of `Config`, but would be a bit harder to understand. Still, remember that `clone` is slow, we are delving into the heap, it will be costly in the longrun. ### Step 3 - Create a Constructor for Config ```rust impl Config { fn new(args: &[String]) -> Config { let query = args[1].clone(); let filename = args[2].clone(); Config { query, filename} } } fn main() { //... let _config = Config::new(&arg_vec); //... } ``` It is better than having do everything everytime we want to create a `Config`, as well as tidier and easier to understand. ## Fixing Error Handling Currently, program panics if we pass less that 2 args. ### Step 1 - Better Error message Check for issues in the `new` method: ```rust fn new(args: &[String]) -> Config { if args.len() < 3 { panic!("Note enough args"); } //... } // Or fn new(args: &[String]) -> Result(Config, Err) { if args.len() < 3{ return Err("Less than 2 arguments passed") } let query = args[1].clone(); let filename = args[2].clone(); Ok(Config { query, filename}) } ``` ### Step 2 - Handling Errors - new returnign Result ```rust use std::process; //... let config = Config::new(&args).unwrap_or_else(|err| { println!("Problem parsing arguments: {}", err); process::exit(1); // Early exit with error number, `closure` }); ``` `unwrap_or_else` opens a scope if the return value is an `Err`, then you can call a bracket and capture the variable held inside err `process::exit(1)` is a closure, early exit ## Extracting logic from main Extract function that does things to data: ```rust run(config); //... fn run(config: &Config) { let contents = fs::read_to_string(config.filename) .expect("Uh Oh"); println!("Test:\n{}", contents); } ``` ### Step 1 - Improve Error Handling ```rust use std::error::Error; // --snip-- fn run(config: Config) -> Result<(), Box> { let contents = fs::read_to_string(config.filename)?; println!("With text:\n{}", contents); Ok(()) } ``` Return a Result instead of allowing the `expect` `panic!` We use the `?` operator that returns the `Err` on error, but continues on `Ok` `Box` Is basically a type that englobes any type of error (Chapter 17). ### Step 2 - Handling errors run in main ```rust if let Err(e) = run(config) { println!("Application error: {}", e); process::exit(1); } // Or run(config).unwrap_or_else(|err| { println!("App Error: {}", err); process::exit(1); }); ``` Use `if let` because we are NOT returning a variable, only on `Err`. We could use an `unwrap_or_else` is a bit wasteful because we are forcing a return value even if we don't use it. `if let` syntax only does error work, else it does not care (would there be any runtime differences in this case?). ## Split into a Library Simple, just take the functionality that is not in `main`, send it to `lib.rs`, set what is needed to `pub` and tada. You can use `use mini_grep` and get everything and use it too. ===