Initial commit
This commit is contained in:
37
solutions/13_error_handling/errors1.rs
Normal file
37
solutions/13_error_handling/errors1.rs
Normal file
@@ -0,0 +1,37 @@
|
||||
fn generate_nametag_text(name: String) -> Result<String, String> {
|
||||
// ^^^^^^ ^^^^^^
|
||||
if name.is_empty() {
|
||||
// `Err(String)` instead of `None`.
|
||||
Err("Empty names aren't allowed".to_string())
|
||||
} else {
|
||||
// `Ok` instead of `Some`.
|
||||
Ok(format!("Hi! My name is {name}"))
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
// You can optionally experiment here.
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn generates_nametag_text_for_a_nonempty_name() {
|
||||
assert_eq!(
|
||||
generate_nametag_text("Beyoncé".to_string()).as_deref(),
|
||||
Ok("Hi! My name is Beyoncé"),
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn explains_why_generating_nametag_text_fails() {
|
||||
assert_eq!(
|
||||
generate_nametag_text(String::new())
|
||||
.as_ref()
|
||||
.map_err(|e| e.as_str()),
|
||||
Err("Empty names aren't allowed"),
|
||||
);
|
||||
}
|
||||
}
|
||||
58
solutions/13_error_handling/errors2.rs
Normal file
58
solutions/13_error_handling/errors2.rs
Normal file
@@ -0,0 +1,58 @@
|
||||
// Say we're writing a game where you can buy items with tokens. All items cost
|
||||
// 5 tokens, and whenever you purchase items there is a processing fee of 1
|
||||
// token. A player of the game will type in how many items they want to buy, and
|
||||
// the `total_cost` function will calculate the total cost of the items. Since
|
||||
// the player typed in the quantity, we get it as a string. They might have
|
||||
// typed anything, not just numbers!
|
||||
//
|
||||
// Right now, this function isn't handling the error case at all. What we want
|
||||
// to do is: If we call the `total_cost` function on a string that is not a
|
||||
// number, that function will return a `ParseIntError`. In that case, we want to
|
||||
// immediately return that error from our function and not try to multiply and
|
||||
// add.
|
||||
//
|
||||
// There are at least two ways to implement this that are both correct. But one
|
||||
// is a lot shorter!
|
||||
|
||||
use std::num::ParseIntError;
|
||||
|
||||
#[allow(unused_variables)]
|
||||
fn total_cost(item_quantity: &str) -> Result<i32, ParseIntError> {
|
||||
let processing_fee = 1;
|
||||
let cost_per_item = 5;
|
||||
|
||||
// Added `?` to propagate the error.
|
||||
let qty = item_quantity.parse::<i32>()?;
|
||||
// ^ added
|
||||
|
||||
// Equivalent to this verbose version:
|
||||
let qty = match item_quantity.parse::<i32>() {
|
||||
Ok(v) => v,
|
||||
Err(e) => return Err(e),
|
||||
};
|
||||
|
||||
Ok(qty * cost_per_item + processing_fee)
|
||||
}
|
||||
|
||||
fn main() {
|
||||
// You can optionally experiment here.
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use std::num::IntErrorKind;
|
||||
|
||||
#[test]
|
||||
fn item_quantity_is_a_valid_number() {
|
||||
assert_eq!(total_cost("34"), Ok(171));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn item_quantity_is_an_invalid_number() {
|
||||
assert_eq!(
|
||||
total_cost("beep boop").unwrap_err().kind(),
|
||||
&IntErrorKind::InvalidDigit,
|
||||
);
|
||||
}
|
||||
}
|
||||
32
solutions/13_error_handling/errors3.rs
Normal file
32
solutions/13_error_handling/errors3.rs
Normal file
@@ -0,0 +1,32 @@
|
||||
// This is a program that is trying to use a completed version of the
|
||||
// `total_cost` function from the previous exercise. It's not working though!
|
||||
// Why not? What should we do to fix it?
|
||||
|
||||
use std::num::ParseIntError;
|
||||
|
||||
// Don't change this function.
|
||||
fn total_cost(item_quantity: &str) -> Result<i32, ParseIntError> {
|
||||
let processing_fee = 1;
|
||||
let cost_per_item = 5;
|
||||
let qty = item_quantity.parse::<i32>()?;
|
||||
|
||||
Ok(qty * cost_per_item + processing_fee)
|
||||
}
|
||||
|
||||
fn main() -> Result<(), ParseIntError> {
|
||||
// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ added
|
||||
let mut tokens = 100;
|
||||
let pretend_user_input = "8";
|
||||
|
||||
let cost = total_cost(pretend_user_input)?;
|
||||
|
||||
if cost > tokens {
|
||||
println!("You can't afford that many!");
|
||||
} else {
|
||||
tokens -= cost;
|
||||
println!("You now have {tokens} tokens.");
|
||||
}
|
||||
|
||||
// Added this line to return the `Ok` variant of the expected `Result`.
|
||||
Ok(())
|
||||
}
|
||||
42
solutions/13_error_handling/errors4.rs
Normal file
42
solutions/13_error_handling/errors4.rs
Normal file
@@ -0,0 +1,42 @@
|
||||
use std::cmp::Ordering;
|
||||
|
||||
#[derive(PartialEq, Debug)]
|
||||
enum CreationError {
|
||||
Negative,
|
||||
Zero,
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Debug)]
|
||||
struct PositiveNonzeroInteger(u64);
|
||||
|
||||
impl PositiveNonzeroInteger {
|
||||
fn new(value: i64) -> Result<Self, CreationError> {
|
||||
match value.cmp(&0) {
|
||||
Ordering::Less => Err(CreationError::Negative),
|
||||
Ordering::Equal => Err(CreationError::Zero),
|
||||
Ordering::Greater => Ok(Self(value as u64)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
// You can optionally experiment here.
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_creation() {
|
||||
assert_eq!(
|
||||
PositiveNonzeroInteger::new(10),
|
||||
Ok(PositiveNonzeroInteger(10)),
|
||||
);
|
||||
assert_eq!(
|
||||
PositiveNonzeroInteger::new(-10),
|
||||
Err(CreationError::Negative),
|
||||
);
|
||||
assert_eq!(PositiveNonzeroInteger::new(0), Err(CreationError::Zero));
|
||||
}
|
||||
}
|
||||
54
solutions/13_error_handling/errors5.rs
Normal file
54
solutions/13_error_handling/errors5.rs
Normal file
@@ -0,0 +1,54 @@
|
||||
// This exercise is an altered version of the `errors4` exercise. It uses some
|
||||
// concepts that we won't get to until later in the course, like `Box` and the
|
||||
// `From` trait. It's not important to understand them in detail right now, but
|
||||
// you can read ahead if you like. For now, think of the `Box<dyn ???>` type as
|
||||
// an "I want anything that does ???" type.
|
||||
//
|
||||
// In short, this particular use case for boxes is for when you want to own a
|
||||
// value and you care only that it is a type which implements a particular
|
||||
// trait. To do so, The `Box` is declared as of type `Box<dyn Trait>` where
|
||||
// `Trait` is the trait the compiler looks for on any value used in that
|
||||
// context. For this exercise, that context is the potential errors which
|
||||
// can be returned in a `Result`.
|
||||
|
||||
use std::error::Error;
|
||||
use std::fmt;
|
||||
|
||||
#[derive(PartialEq, Debug)]
|
||||
enum CreationError {
|
||||
Negative,
|
||||
Zero,
|
||||
}
|
||||
|
||||
// This is required so that `CreationError` can implement `Error`.
|
||||
impl fmt::Display for CreationError {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
let description = match *self {
|
||||
CreationError::Negative => "number is negative",
|
||||
CreationError::Zero => "number is zero",
|
||||
};
|
||||
f.write_str(description)
|
||||
}
|
||||
}
|
||||
|
||||
impl Error for CreationError {}
|
||||
|
||||
#[derive(PartialEq, Debug)]
|
||||
struct PositiveNonzeroInteger(u64);
|
||||
|
||||
impl PositiveNonzeroInteger {
|
||||
fn new(value: i64) -> Result<PositiveNonzeroInteger, CreationError> {
|
||||
match value {
|
||||
x if x < 0 => Err(CreationError::Negative),
|
||||
0 => Err(CreationError::Zero),
|
||||
x => Ok(PositiveNonzeroInteger(x as u64)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn main() -> Result<(), Box<dyn Error>> {
|
||||
let pretend_user_input = "42";
|
||||
let x: i64 = pretend_user_input.parse()?;
|
||||
println!("output={:?}", PositiveNonzeroInteger::new(x)?);
|
||||
Ok(())
|
||||
}
|
||||
91
solutions/13_error_handling/errors6.rs
Normal file
91
solutions/13_error_handling/errors6.rs
Normal file
@@ -0,0 +1,91 @@
|
||||
// Using catch-all error types like `Box<dyn Error>` isn't recommended for
|
||||
// library code where callers might want to make decisions based on the error
|
||||
// content instead of printing it out or propagating it further. Here, we define
|
||||
// a custom error type to make it possible for callers to decide what to do next
|
||||
// when our function returns an error.
|
||||
|
||||
use std::num::ParseIntError;
|
||||
|
||||
#[derive(PartialEq, Debug)]
|
||||
enum CreationError {
|
||||
Negative,
|
||||
Zero,
|
||||
}
|
||||
|
||||
// A custom error type that we will be using in `PositiveNonzeroInteger::parse`.
|
||||
#[derive(PartialEq, Debug)]
|
||||
enum ParsePosNonzeroError {
|
||||
Creation(CreationError),
|
||||
ParseInt(ParseIntError),
|
||||
}
|
||||
|
||||
impl ParsePosNonzeroError {
|
||||
fn from_creation(err: CreationError) -> Self {
|
||||
Self::Creation(err)
|
||||
}
|
||||
|
||||
fn from_parse_int(err: ParseIntError) -> Self {
|
||||
Self::ParseInt(err)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Debug)]
|
||||
struct PositiveNonzeroInteger(u64);
|
||||
|
||||
impl PositiveNonzeroInteger {
|
||||
fn new(value: i64) -> Result<Self, CreationError> {
|
||||
match value {
|
||||
x if x < 0 => Err(CreationError::Negative),
|
||||
0 => Err(CreationError::Zero),
|
||||
x => Ok(Self(x as u64)),
|
||||
}
|
||||
}
|
||||
|
||||
fn parse(s: &str) -> Result<Self, ParsePosNonzeroError> {
|
||||
// Return an appropriate error instead of panicking when `parse()`
|
||||
// returns an error.
|
||||
let x: i64 = s.parse().map_err(ParsePosNonzeroError::from_parse_int)?;
|
||||
// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
Self::new(x).map_err(ParsePosNonzeroError::from_creation)
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
// You can optionally experiment here.
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_parse_error() {
|
||||
assert!(matches!(
|
||||
PositiveNonzeroInteger::parse("not a number"),
|
||||
Err(ParsePosNonzeroError::ParseInt(_)),
|
||||
));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_negative() {
|
||||
assert_eq!(
|
||||
PositiveNonzeroInteger::parse("-555"),
|
||||
Err(ParsePosNonzeroError::Creation(CreationError::Negative)),
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_zero() {
|
||||
assert_eq!(
|
||||
PositiveNonzeroInteger::parse("0"),
|
||||
Err(ParsePosNonzeroError::Creation(CreationError::Zero)),
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_positive() {
|
||||
let x = PositiveNonzeroInteger::new(42).unwrap();
|
||||
assert_eq!(x.0, 42);
|
||||
assert_eq!(PositiveNonzeroInteger::parse("42"), Ok(x));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user