Going through rustlings for the first time

ex(13): error handling

+8 -2
.rustlings-state.txt
··· 1 1 DON'T EDIT THIS FILE! 2 2 3 - errors1 3 + generics1 4 4 5 5 intro1 6 6 intro2 ··· 51 51 quiz2 52 52 options1 53 53 options2 54 - options3 54 + options3 55 + errors1 56 + errors2 57 + errors3 58 + errors4 59 + errors5 60 + errors6
+3 -3
exercises/13_error_handling/errors1.rs
··· 4 4 // construct to `Option` that can be used to express error conditions. Change 5 5 // the function signature and body to return `Result<String, String>` instead 6 6 // of `Option<String>`. 7 - fn generate_nametag_text(name: String) -> Option<String> { 7 + fn generate_nametag_text(name: String) -> Result<String, String> { 8 8 if name.is_empty() { 9 9 // Empty names aren't allowed 10 - None 10 + Err(String::from("Empty names aren't allowed")) 11 11 } else { 12 - Some(format!("Hi! My name is {name}")) 12 + Ok(format!("Hi! My name is {name}")) 13 13 } 14 14 } 15 15
+1 -1
exercises/13_error_handling/errors2.rs
··· 21 21 let cost_per_item = 5; 22 22 23 23 // TODO: Handle the error case as described above. 24 - let qty = item_quantity.parse::<i32>(); 24 + let qty = item_quantity.parse::<i32>()?; 25 25 26 26 Ok(qty * cost_per_item + processing_fee) 27 27 }
+4 -2
exercises/13_error_handling/errors3.rs
··· 15 15 16 16 // TODO: Fix the compiler error by changing the signature and body of the 17 17 // `main` function. 18 - fn main() { 18 + fn main() -> Result<(), ParseIntError> { 19 19 let mut tokens = 100; 20 20 let pretend_user_input = "8"; 21 21 ··· 27 27 } else { 28 28 tokens -= cost; 29 29 println!("You now have {tokens} tokens."); 30 - } 30 + }; 31 + 32 + Ok(()) 31 33 }
+7 -1
exercises/13_error_handling/errors4.rs
··· 1 + use std::cmp::Ordering; 2 + 1 3 #[derive(PartialEq, Debug)] 2 4 enum CreationError { 3 5 Negative, ··· 10 12 impl PositiveNonzeroInteger { 11 13 fn new(value: i64) -> Result<Self, CreationError> { 12 14 // TODO: This function shouldn't always return an `Ok`. 13 - Ok(Self(value as u64)) 15 + match value.cmp(&0) { 16 + Ordering::Greater => Ok(Self(value as u64)), 17 + Ordering::Equal => Err(CreationError::Zero), 18 + Ordering::Less => Err(CreationError::Negative), 19 + } 14 20 } 15 21 } 16 22
+1 -1
exercises/13_error_handling/errors5.rs
··· 48 48 49 49 // TODO: Add the correct return type `Result<(), Box<dyn ???>>`. What can we 50 50 // use to describe both errors? Is there a trait which both errors implement? 51 - fn main() { 51 + fn main() -> Result<(), Box<dyn Error>> { 52 52 let pretend_user_input = "42"; 53 53 let x: i64 = pretend_user_input.parse()?; 54 54 println!("output={:?}", PositiveNonzeroInteger::new(x)?);
+4 -2
exercises/13_error_handling/errors6.rs
··· 25 25 } 26 26 27 27 // TODO: Add another error conversion function here. 28 - // fn from_parse_int(???) -> Self { ??? } 28 + fn from_parse_int(err: ParseIntError) -> Self { 29 + Self::ParseInt(err) 30 + } 29 31 } 30 32 31 33 #[derive(PartialEq, Debug)] ··· 43 45 fn parse(s: &str) -> Result<Self, ParsePosNonzeroError> { 44 46 // TODO: change this to return an appropriate error instead of panicking 45 47 // when `parse()` returns an error. 46 - let x: i64 = s.parse().unwrap(); 48 + let x: i64 = s.parse().map_err(ParsePosNonzeroError::from_parse_int)?; 47 49 Self::new(x).map_err(ParsePosNonzeroError::from_creation) 48 50 } 49 51 }
+35 -2
solutions/13_error_handling/errors1.rs
··· 1 + fn generate_nametag_text(name: String) -> Result<String, String> { 2 + // ^^^^^^ ^^^^^^ 3 + if name.is_empty() { 4 + // `Err(String)` instead of `None`. 5 + Err("Empty names aren't allowed".to_string()) 6 + } else { 7 + // `Ok` instead of `Some`. 8 + Ok(format!("Hi! My name is {name}")) 9 + } 10 + } 11 + 1 12 fn main() { 2 - // DON'T EDIT THIS SOLUTION FILE! 3 - // It will be automatically filled after you finish the exercise. 13 + // You can optionally experiment here. 14 + } 15 + 16 + #[cfg(test)] 17 + mod tests { 18 + use super::*; 19 + 20 + #[test] 21 + fn generates_nametag_text_for_a_nonempty_name() { 22 + assert_eq!( 23 + generate_nametag_text("Beyoncé".to_string()).as_deref(), 24 + Ok("Hi! My name is Beyoncé"), 25 + ); 26 + } 27 + 28 + #[test] 29 + fn explains_why_generating_nametag_text_fails() { 30 + assert_eq!( 31 + generate_nametag_text(String::new()) 32 + .as_ref() 33 + .map_err(|e| e.as_str()), 34 + Err("Empty names aren't allowed"), 35 + ); 36 + } 4 37 }
+56 -2
solutions/13_error_handling/errors2.rs
··· 1 + // Say we're writing a game where you can buy items with tokens. All items cost 2 + // 5 tokens, and whenever you purchase items there is a processing fee of 1 3 + // token. A player of the game will type in how many items they want to buy, and 4 + // the `total_cost` function will calculate the total cost of the items. Since 5 + // the player typed in the quantity, we get it as a string. They might have 6 + // typed anything, not just numbers! 7 + // 8 + // Right now, this function isn't handling the error case at all. What we want 9 + // to do is: If we call the `total_cost` function on a string that is not a 10 + // number, that function will return a `ParseIntError`. In that case, we want to 11 + // immediately return that error from our function and not try to multiply and 12 + // add. 13 + // 14 + // There are at least two ways to implement this that are both correct. But one 15 + // is a lot shorter! 16 + 17 + use std::num::ParseIntError; 18 + 19 + #[allow(unused_variables)] 20 + fn total_cost(item_quantity: &str) -> Result<i32, ParseIntError> { 21 + let processing_fee = 1; 22 + let cost_per_item = 5; 23 + 24 + // Added `?` to propagate the error. 25 + let qty = item_quantity.parse::<i32>()?; 26 + // ^ added 27 + 28 + // Equivalent to this verbose version: 29 + let qty = match item_quantity.parse::<i32>() { 30 + Ok(v) => v, 31 + Err(e) => return Err(e), 32 + }; 33 + 34 + Ok(qty * cost_per_item + processing_fee) 35 + } 36 + 1 37 fn main() { 2 - // DON'T EDIT THIS SOLUTION FILE! 3 - // It will be automatically filled after you finish the exercise. 38 + // You can optionally experiment here. 39 + } 40 + 41 + #[cfg(test)] 42 + mod tests { 43 + use super::*; 44 + use std::num::IntErrorKind; 45 + 46 + #[test] 47 + fn item_quantity_is_a_valid_number() { 48 + assert_eq!(total_cost("34"), Ok(171)); 49 + } 50 + 51 + #[test] 52 + fn item_quantity_is_an_invalid_number() { 53 + assert_eq!( 54 + total_cost("beep boop").unwrap_err().kind(), 55 + &IntErrorKind::InvalidDigit, 56 + ); 57 + } 4 58 }
+31 -3
solutions/13_error_handling/errors3.rs
··· 1 - fn main() { 2 - // DON'T EDIT THIS SOLUTION FILE! 3 - // It will be automatically filled after you finish the exercise. 1 + // This is a program that is trying to use a completed version of the 2 + // `total_cost` function from the previous exercise. It's not working though! 3 + // Why not? What should we do to fix it? 4 + 5 + use std::num::ParseIntError; 6 + 7 + // Don't change this function. 8 + fn total_cost(item_quantity: &str) -> Result<i32, ParseIntError> { 9 + let processing_fee = 1; 10 + let cost_per_item = 5; 11 + let qty = item_quantity.parse::<i32>()?; 12 + 13 + Ok(qty * cost_per_item + processing_fee) 14 + } 15 + 16 + fn main() -> Result<(), ParseIntError> { 17 + // ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ added 18 + let mut tokens = 100; 19 + let pretend_user_input = "8"; 20 + 21 + let cost = total_cost(pretend_user_input)?; 22 + 23 + if cost > tokens { 24 + println!("You can't afford that many!"); 25 + } else { 26 + tokens -= cost; 27 + println!("You now have {tokens} tokens."); 28 + } 29 + 30 + // Added this line to return the `Ok` variant of the expected `Result`. 31 + Ok(()) 4 32 }
+40 -2
solutions/13_error_handling/errors4.rs
··· 1 + use std::cmp::Ordering; 2 + 3 + #[derive(PartialEq, Debug)] 4 + enum CreationError { 5 + Negative, 6 + Zero, 7 + } 8 + 9 + #[derive(PartialEq, Debug)] 10 + struct PositiveNonzeroInteger(u64); 11 + 12 + impl PositiveNonzeroInteger { 13 + fn new(value: i64) -> Result<Self, CreationError> { 14 + match value.cmp(&0) { 15 + Ordering::Less => Err(CreationError::Negative), 16 + Ordering::Equal => Err(CreationError::Zero), 17 + Ordering::Greater => Ok(Self(value as u64)), 18 + } 19 + } 20 + } 21 + 1 22 fn main() { 2 - // DON'T EDIT THIS SOLUTION FILE! 3 - // It will be automatically filled after you finish the exercise. 23 + // You can optionally experiment here. 24 + } 25 + 26 + #[cfg(test)] 27 + mod tests { 28 + use super::*; 29 + 30 + #[test] 31 + fn test_creation() { 32 + assert_eq!( 33 + PositiveNonzeroInteger::new(10), 34 + Ok(PositiveNonzeroInteger(10)), 35 + ); 36 + assert_eq!( 37 + PositiveNonzeroInteger::new(-10), 38 + Err(CreationError::Negative), 39 + ); 40 + assert_eq!(PositiveNonzeroInteger::new(0), Err(CreationError::Zero)); 41 + } 4 42 }
+53 -3
solutions/13_error_handling/errors5.rs
··· 1 - fn main() { 2 - // DON'T EDIT THIS SOLUTION FILE! 3 - // It will be automatically filled after you finish the exercise. 1 + // This exercise is an altered version of the `errors4` exercise. It uses some 2 + // concepts that we won't get to until later in the course, like `Box` and the 3 + // `From` trait. It's not important to understand them in detail right now, but 4 + // you can read ahead if you like. For now, think of the `Box<dyn ???>` type as 5 + // an "I want anything that does ???" type. 6 + // 7 + // In short, this particular use case for boxes is for when you want to own a 8 + // value and you care only that it is a type which implements a particular 9 + // trait. To do so, The `Box` is declared as of type `Box<dyn Trait>` where 10 + // `Trait` is the trait the compiler looks for on any value used in that 11 + // context. For this exercise, that context is the potential errors which 12 + // can be returned in a `Result`. 13 + 14 + use std::error::Error; 15 + use std::fmt; 16 + 17 + #[derive(PartialEq, Debug)] 18 + enum CreationError { 19 + Negative, 20 + Zero, 21 + } 22 + 23 + // This is required so that `CreationError` can implement `Error`. 24 + impl fmt::Display for CreationError { 25 + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 26 + let description = match *self { 27 + CreationError::Negative => "number is negative", 28 + CreationError::Zero => "number is zero", 29 + }; 30 + f.write_str(description) 31 + } 32 + } 33 + 34 + impl Error for CreationError {} 35 + 36 + #[derive(PartialEq, Debug)] 37 + struct PositiveNonzeroInteger(u64); 38 + 39 + impl PositiveNonzeroInteger { 40 + fn new(value: i64) -> Result<PositiveNonzeroInteger, CreationError> { 41 + match value { 42 + x if x < 0 => Err(CreationError::Negative), 43 + 0 => Err(CreationError::Zero), 44 + x => Ok(PositiveNonzeroInteger(x as u64)), 45 + } 46 + } 47 + } 48 + 49 + fn main() -> Result<(), Box<dyn Error>> { 50 + let pretend_user_input = "42"; 51 + let x: i64 = pretend_user_input.parse()?; 52 + println!("output={:?}", PositiveNonzeroInteger::new(x)?); 53 + Ok(()) 4 54 }
+89 -2
solutions/13_error_handling/errors6.rs
··· 1 + // Using catch-all error types like `Box<dyn Error>` isn't recommended for 2 + // library code where callers might want to make decisions based on the error 3 + // content instead of printing it out or propagating it further. Here, we define 4 + // a custom error type to make it possible for callers to decide what to do next 5 + // when our function returns an error. 6 + 7 + use std::num::ParseIntError; 8 + 9 + #[derive(PartialEq, Debug)] 10 + enum CreationError { 11 + Negative, 12 + Zero, 13 + } 14 + 15 + // A custom error type that we will be using in `PositiveNonzeroInteger::parse`. 16 + #[derive(PartialEq, Debug)] 17 + enum ParsePosNonzeroError { 18 + Creation(CreationError), 19 + ParseInt(ParseIntError), 20 + } 21 + 22 + impl ParsePosNonzeroError { 23 + fn from_creation(err: CreationError) -> Self { 24 + Self::Creation(err) 25 + } 26 + 27 + fn from_parse_int(err: ParseIntError) -> Self { 28 + Self::ParseInt(err) 29 + } 30 + } 31 + 32 + #[derive(PartialEq, Debug)] 33 + struct PositiveNonzeroInteger(u64); 34 + 35 + impl PositiveNonzeroInteger { 36 + fn new(value: i64) -> Result<Self, CreationError> { 37 + match value { 38 + x if x < 0 => Err(CreationError::Negative), 39 + 0 => Err(CreationError::Zero), 40 + x => Ok(Self(x as u64)), 41 + } 42 + } 43 + 44 + fn parse(s: &str) -> Result<Self, ParsePosNonzeroError> { 45 + // Return an appropriate error instead of panicking when `parse()` 46 + // returns an error. 47 + let x: i64 = s.parse().map_err(ParsePosNonzeroError::from_parse_int)?; 48 + // ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 49 + Self::new(x).map_err(ParsePosNonzeroError::from_creation) 50 + } 51 + } 52 + 1 53 fn main() { 2 - // DON'T EDIT THIS SOLUTION FILE! 3 - // It will be automatically filled after you finish the exercise. 54 + // You can optionally experiment here. 55 + } 56 + 57 + #[cfg(test)] 58 + mod test { 59 + use super::*; 60 + 61 + #[test] 62 + fn test_parse_error() { 63 + assert!(matches!( 64 + PositiveNonzeroInteger::parse("not a number"), 65 + Err(ParsePosNonzeroError::ParseInt(_)), 66 + )); 67 + } 68 + 69 + #[test] 70 + fn test_negative() { 71 + assert_eq!( 72 + PositiveNonzeroInteger::parse("-555"), 73 + Err(ParsePosNonzeroError::Creation(CreationError::Negative)), 74 + ); 75 + } 76 + 77 + #[test] 78 + fn test_zero() { 79 + assert_eq!( 80 + PositiveNonzeroInteger::parse("0"), 81 + Err(ParsePosNonzeroError::Creation(CreationError::Zero)), 82 + ); 83 + } 84 + 85 + #[test] 86 + fn test_positive() { 87 + let x = PositiveNonzeroInteger::new(42).unwrap(); 88 + assert_eq!(x.0, 42); 89 + assert_eq!(PositiveNonzeroInteger::parse("42"), Ok(x)); 90 + } 4 91 }