errors1.rs#
// errors1.rs
// This function refuses to generate text to be printed on a nametag if
// you pass it an empty string. It'd be nicer if it explained what the problem
// was, instead of just sometimes returning `None`. Thankfully, Rust has a similar
// construct to `Option` that can be used to express error conditions. Let's use it!
// Execute `rustlings hint errors1` or use the `hint` watch subcommand for a hint.
pub fn generate_nametag_text(name: String) -> Result<String, String> {
if name.is_empty() {
// Empty names aren't allowed.
Err(format!("`name` was empty; it must be nonempty."))
} else {
Ok(format!("Hi! My name is {}", name))
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn generates_nametag_text_for_a_nonempty_name() {
assert_eq!(
generate_nametag_text("Beyoncé".into()),
Ok("Hi! My name is Beyoncé".into())
);
}
#[test]
fn explains_why_generating_nametag_text_fails() {
assert_eq!(
generate_nametag_text("".into()),
// Don't change this line
Err("`name` was empty; it must be nonempty.".into())
);
}
}
errors2.rs#
// errors2.rs
// 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 number of tokens.
// Since the player typed in the quantity, though, we get it as a string-- and
// they might have typed anything, not just numbers!
// Right now, this function isn't handling the error case at all (and isn't
// handling the success case properly either). What we want to do is:
// if we call the `parse` function on a string that is not a number, that
// function will return a `ParseIntError`, and 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!
// Execute `rustlings hint errors2` or use the `hint` watch subcommand for a hint.
use std::num::ParseIntError;
pub 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)
}
#[cfg(test)]
mod tests {
use super::*;
#[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().to_string(),
"invalid digit found in string"
);
}
}
errors3.rs#
// errors3.rs
// 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?
// Execute `rustlings hint errors3` or use the `hint` watch subcommand for a hint.
use std::num::ParseIntError;
fn main() -> Result<(), ParseIntError> {
let mut tokens = 100;
let pretend_user_input = "8";
let cost = total_cost(pretend_user_input)?;
if cost > tokens {
Ok(println!("You can't afford that many!"))
} else {
tokens -= cost;
Ok(println!("You now have {} tokens.", tokens))
}
}
pub 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)
}
errors4.rs#
// errors4.rs
// Execute `rustlings hint errors4` or use the `hint` watch subcommand for a hint.
#[derive(PartialEq, Debug)]
struct PositiveNonzeroInteger(u64);
#[derive(PartialEq, Debug)]
enum CreationError {
Negative,
Zero,
}
impl PositiveNonzeroInteger {
fn new(value: i64) -> Result<PositiveNonzeroInteger, CreationError> {
// Hmm...? Why is this only returning an Ok value?
if value > 0 {
Ok(PositiveNonzeroInteger(value as u64))
} else if value == 0 {
Err(CreationError::Zero)
} else {
Err(CreationError::Negative)
}
}
}
#[test]
fn test_creation() {
assert!(PositiveNonzeroInteger::new(10).is_ok());
assert_eq!(
Err(CreationError::Negative),
PositiveNonzeroInteger::new(-10)
);
assert_eq!(Err(CreationError::Zero), PositiveNonzeroInteger::new(0));
}
errors5#
// errors5.rs
// This program uses an altered version of the code from errors4.
// This exercise 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, which, given
// Rust's usual standards for runtime safety, should strike you as somewhat lenient!
// 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.
// What can we use to describe both errors? In other words, is there a trait which both errors implement?
// Execute `rustlings hint errors5` or use the `hint` watch subcommand for a hint.
use std::error;
use std::fmt;
use std::num::ParseIntError;
fn main() -> Result<(), Box<dyn error::Error>> {
let pretend_user_input = "42";
let x: i64 = pretend_user_input.parse()?;
println!("output={:?}", PositiveNonzeroInteger::new(x)?);
Ok(())
}
// Don't change anything below this line.
#[derive(PartialEq, Debug)]
struct PositiveNonzeroInteger(u64);
#[derive(PartialEq, Debug)]
enum CreationError {
Negative,
Zero,
}
impl PositiveNonzeroInteger {
fn new(value: i64) -> Result<PositiveNonzeroInteger, CreationError> {
match value {
x if x < 0 => Err(CreationError::Negative),
x if x == 0 => Err(CreationError::Zero),
x => Ok(PositiveNonzeroInteger(x as u64)),
}
}
}
// This is required so that `CreationError` can implement `error::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::Error for CreationError {}
errors6.rs#
// errors6.rs
// Using catch-all error types like `Box<dyn error::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.
// Execute `rustlings hint errors6` or use the `hint` watch subcommand for a hint.
use std::num::ParseIntError;
// This is a custom error type that we will be using in `parse_pos_nonzero()`.
#[derive(PartialEq, Debug)]
enum ParsePosNonzeroError {
Creation(CreationError),
ParseInt(ParseIntError),
}
impl ParsePosNonzeroError {
fn from_creation(err: CreationError) -> ParsePosNonzeroError {
ParsePosNonzeroError::Creation(err)
}
fn from_parseint(err: ParseIntError) -> ParsePosNonzeroError {
ParsePosNonzeroError::ParseInt(err)
}
}
fn parse_pos_nonzero(s: &str) -> Result<PositiveNonzeroInteger, ParsePosNonzeroError> {
let x: i64 = s.parse().map_err(ParsePosNonzeroError::from_parseint)?;
PositiveNonzeroInteger::new(x).map_err(ParsePosNonzeroError::from_creation)
}
// Don't change anything below this line.
#[derive(PartialEq, Debug)]
struct PositiveNonzeroInteger(u64);
#[derive(PartialEq, Debug)]
enum CreationError {
Negative,
Zero,
}
impl PositiveNonzeroInteger {
fn new(value: i64) -> Result<PositiveNonzeroInteger, CreationError> {
match value {
x if x < 0 => Err(CreationError::Negative),
x if x == 0 => Err(CreationError::Zero),
x => Ok(PositiveNonzeroInteger(x as u64)),
}
}
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn test_parse_error() {
// We can't construct a ParseIntError, so we have to pattern match.
assert!(matches!(
parse_pos_nonzero("not a number"),
Err(ParsePosNonzeroError::ParseInt(_))
));
}
#[test]
fn test_negative() {
assert_eq!(
parse_pos_nonzero("-555"),
Err(ParsePosNonzeroError::Creation(CreationError::Negative))
);
}
#[test]
fn test_zero() {
assert_eq!(
parse_pos_nonzero("0"),
Err(ParsePosNonzeroError::Creation(CreationError::Zero))
);
}
#[test]
fn test_positive() {
let x = PositiveNonzeroInteger::new(42);
assert!(x.is_ok());
assert_eq!(parse_pos_nonzero("42"), Ok(x.unwrap()));
}
}