finish errors

This commit is contained in:
Denis-Cosmin Nutiu 2024-10-31 22:27:56 +02:00
parent 63d7568a22
commit 7e6588f6f0
13 changed files with 344 additions and 32 deletions

View file

@ -1,6 +1,6 @@
DON'T EDIT THIS FILE! DON'T EDIT THIS FILE!
errors1 generics1
intro1 intro1
intro2 intro2
@ -52,3 +52,9 @@ quiz2
options1 options1
options2 options2
options3 options3
errors1
errors2
errors3
errors4
errors5
errors6

View file

@ -4,12 +4,12 @@
// construct to `Option` that can be used to express error conditions. Change // construct to `Option` that can be used to express error conditions. Change
// the function signature and body to return `Result<String, String>` instead // the function signature and body to return `Result<String, String>` instead
// of `Option<String>`. // of `Option<String>`.
fn generate_nametag_text(name: String) -> Option<String> { fn generate_nametag_text(name: String) -> Result<String, String> {
if name.is_empty() { if name.is_empty() {
// Empty names aren't allowed // Empty names aren't allowed
None Err("Empty names aren't allowed".to_string())
} else { } else {
Some(format!("Hi! My name is {name}")) Ok(format!("Hi! My name is {name}"))
} }
} }

View file

@ -23,7 +23,16 @@ fn total_cost(item_quantity: &str) -> Result<i32, ParseIntError> {
// TODO: Handle the error case as described above. // TODO: Handle the error case as described above.
let qty = item_quantity.parse::<i32>(); let qty = item_quantity.parse::<i32>();
Ok(qty * cost_per_item + processing_fee) match qty {
Ok(value) => {
Ok(value * cost_per_item + processing_fee)
}
Err(err) => {
Err(err)
}
}
} }
fn main() { fn main() {

View file

@ -15,7 +15,7 @@ fn total_cost(item_quantity: &str) -> Result<i32, ParseIntError> {
// TODO: Fix the compiler error by changing the signature and body of the // TODO: Fix the compiler error by changing the signature and body of the
// `main` function. // `main` function.
fn main() { fn main() -> Result<(), Box<dyn std::error::Error>> {
let mut tokens = 100; let mut tokens = 100;
let pretend_user_input = "8"; let pretend_user_input = "8";
@ -28,4 +28,5 @@ fn main() {
tokens -= cost; tokens -= cost;
println!("You now have {tokens} tokens."); println!("You now have {tokens} tokens.");
} }
Ok(())
} }

View file

@ -9,7 +9,12 @@ struct PositiveNonzeroInteger(u64);
impl PositiveNonzeroInteger { impl PositiveNonzeroInteger {
fn new(value: i64) -> Result<Self, CreationError> { fn new(value: i64) -> Result<Self, CreationError> {
// TODO: This function shouldn't always return an `Ok`. if value < 0 {
return Err(CreationError::Negative);
}
if value == 0 {
return Err(CreationError::Zero);
}
Ok(Self(value as u64)) Ok(Self(value as u64))
} }
} }

View file

@ -48,7 +48,7 @@ impl PositiveNonzeroInteger {
// TODO: Add the correct return type `Result<(), Box<dyn ???>>`. What can we // TODO: Add the correct return type `Result<(), Box<dyn ???>>`. What can we
// use to describe both errors? Is there a trait which both errors implement? // use to describe both errors? Is there a trait which both errors implement?
fn main() { fn main() -> Result<(), Box<dyn Error>> {
let pretend_user_input = "42"; let pretend_user_input = "42";
let x: i64 = pretend_user_input.parse()?; let x: i64 = pretend_user_input.parse()?;
println!("output={:?}", PositiveNonzeroInteger::new(x)?); println!("output={:?}", PositiveNonzeroInteger::new(x)?);

View file

@ -24,8 +24,9 @@ impl ParsePosNonzeroError {
Self::Creation(err) Self::Creation(err)
} }
// TODO: Add another error conversion function here. fn from_parse_int(err: ParseIntError) -> Self {
// fn from_parse_int(???) -> Self { ??? } Self::ParseInt(err)
}
} }
#[derive(PartialEq, Debug)] #[derive(PartialEq, Debug)]
@ -41,9 +42,9 @@ impl PositiveNonzeroInteger {
} }
fn parse(s: &str) -> Result<Self, ParsePosNonzeroError> { fn parse(s: &str) -> Result<Self, ParsePosNonzeroError> {
// TODO: change this to return an appropriate error instead of panicking let x: i64 = s.parse().map_err(|e| {
// when `parse()` returns an error. ParsePosNonzeroError::from_parse_int(e)
let x: i64 = s.parse().unwrap(); })?;
Self::new(x).map_err(ParsePosNonzeroError::from_creation) Self::new(x).map_err(ParsePosNonzeroError::from_creation)
} }
} }

View file

@ -1,4 +1,37 @@
fn main() { fn generate_nametag_text(name: String) -> Result<String, String> {
// DON'T EDIT THIS SOLUTION FILE! // ^^^^^^ ^^^^^^
// It will be automatically filled after you finish the exercise. 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"),
);
}
} }

View file

@ -1,4 +1,58 @@
fn main() { // Say we're writing a game where you can buy items with tokens. All items cost
// DON'T EDIT THIS SOLUTION FILE! // 5 tokens, and whenever you purchase items there is a processing fee of 1
// It will be automatically filled after you finish the exercise. // 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,
);
}
} }

View file

@ -1,4 +1,32 @@
fn main() { // This is a program that is trying to use a completed version of the
// DON'T EDIT THIS SOLUTION FILE! // `total_cost` function from the previous exercise. It's not working though!
// It will be automatically filled after you finish the exercise. // 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(())
} }

View file

@ -1,4 +1,42 @@
fn main() { use std::cmp::Ordering;
// DON'T EDIT THIS SOLUTION FILE!
// It will be automatically filled after you finish the exercise. #[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));
}
} }

View file

@ -1,4 +1,54 @@
fn main() { // This exercise is an altered version of the `errors4` exercise. It uses some
// DON'T EDIT THIS SOLUTION FILE! // concepts that we won't get to until later in the course, like `Box` and the
// It will be automatically filled after you finish the exercise. // `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(())
} }

View file

@ -1,4 +1,91 @@
fn main() { // Using catch-all error types like `Box<dyn Error>` isn't recommended for
// DON'T EDIT THIS SOLUTION FILE! // library code where callers might want to make decisions based on the error
// It will be automatically filled after you finish the exercise. // 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));
}
} }