Compare commits

..

No commits in common. "129884aff74964d13aba8309014554b5625d6e5b" and "1694682aa4ee848033169449a3f64d2e163f5638" have entirely different histories.

19 changed files with 166 additions and 491 deletions

50
Cargo.lock generated
View file

@ -113,9 +113,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
[[package]] [[package]]
name = "bitflags" name = "bitflags"
version = "2.6.0" version = "2.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" checksum = "cf4b9d6a944f767f8e5e0db018570623c85f3d925ac718db4e06d0187adb21c1"
[[package]] [[package]]
name = "bstr" name = "bstr"
@ -229,7 +229,7 @@ version = "0.27.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f476fe445d41c9e991fd07515a6f463074b782242ccf4a5b7b1d1012e70824df" checksum = "f476fe445d41c9e991fd07515a6f463074b782242ccf4a5b7b1d1012e70824df"
dependencies = [ dependencies = [
"bitflags 2.6.0", "bitflags 2.5.0",
"crossterm_winapi", "crossterm_winapi",
"libc", "libc",
"mio", "mio",
@ -262,9 +262,9 @@ checksum = "fea41bba32d969b513997752735605054bc0dfa92b4c56bf1189f2e174be7a10"
[[package]] [[package]]
name = "either" name = "either"
version = "1.13.0" version = "1.12.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" checksum = "3dca9240753cf90908d7e4aac30f630662b02aebaa1b58a3cadabdb23385b58b"
[[package]] [[package]]
name = "equivalent" name = "equivalent"
@ -363,15 +363,6 @@ dependencies = [
"either", "either",
] ]
[[package]]
name = "itertools"
version = "0.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186"
dependencies = [
"either",
]
[[package]] [[package]]
name = "itoa" name = "itoa"
version = "1.0.11" version = "1.0.11"
@ -459,7 +450,7 @@ version = "6.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6205bd8bb1e454ad2e27422015fb5e4f2bcc7e08fa8f27058670d208324a4d2d" checksum = "6205bd8bb1e454ad2e27422015fb5e4f2bcc7e08fa8f27058670d208324a4d2d"
dependencies = [ dependencies = [
"bitflags 2.6.0", "bitflags 2.5.0",
"crossbeam-channel", "crossbeam-channel",
"filetime", "filetime",
"fsevent-sys", "fsevent-sys",
@ -568,9 +559,9 @@ dependencies = [
[[package]] [[package]]
name = "proc-macro2" name = "proc-macro2"
version = "1.0.86" version = "1.0.85"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5e719e8df665df0d1c8fbfd238015744736151d4445ec0836b8e628aae103b77" checksum = "22244ce15aa966053a896d1accb3a6e68469b97c7f33f284b99f0d576879fc23"
dependencies = [ dependencies = [
"unicode-ident", "unicode-ident",
] ]
@ -586,20 +577,19 @@ dependencies = [
[[package]] [[package]]
name = "ratatui" name = "ratatui"
version = "0.27.0" version = "0.26.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d16546c5b5962abf8ce6e2881e722b4e0ae3b6f1a08a26ae3573c55853ca68d3" checksum = "f44c9e68fd46eda15c646fbb85e1040b657a58cdc8c98db1d97a55930d991eef"
dependencies = [ dependencies = [
"bitflags 2.6.0", "bitflags 2.5.0",
"cassowary", "cassowary",
"compact_str", "compact_str",
"crossterm", "crossterm",
"itertools 0.13.0", "itertools",
"lru", "lru",
"paste", "paste",
"stability", "stability",
"strum", "strum",
"strum_macros",
"unicode-segmentation", "unicode-segmentation",
"unicode-truncate", "unicode-truncate",
"unicode-width", "unicode-width",
@ -620,7 +610,7 @@ version = "0.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c82cf8cff14456045f55ec4241383baeff27af886adb72ffb2162f99911de0fd" checksum = "c82cf8cff14456045f55ec4241383baeff27af886adb72ffb2162f99911de0fd"
dependencies = [ dependencies = [
"bitflags 2.6.0", "bitflags 2.5.0",
] ]
[[package]] [[package]]
@ -729,9 +719,9 @@ dependencies = [
[[package]] [[package]]
name = "serde_json" name = "serde_json"
version = "1.0.118" version = "1.0.117"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d947f6b3163d8857ea16c4fa0dd4840d52f3041039a85decd46867eb1abef2e4" checksum = "455182ea6142b14f93f4bc5320a2b31c1f266b66a4a5c858b013302a5d8cbfc3"
dependencies = [ dependencies = [
"itoa", "itoa",
"ryu", "ryu",
@ -807,9 +797,9 @@ checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
[[package]] [[package]]
name = "strum" name = "strum"
version = "0.26.3" version = "0.26.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8fec0f0aef304996cf250b31b5a10dee7980c85da9d759361292b8bca5a18f06" checksum = "5d8cec3501a5194c432b2b7976db6b7d10ec95c253208b45f83f7136aa985e29"
dependencies = [ dependencies = [
"strum_macros", "strum_macros",
] ]
@ -829,9 +819,9 @@ dependencies = [
[[package]] [[package]]
name = "syn" name = "syn"
version = "2.0.68" version = "2.0.66"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "901fa70d88b9d6c98022e23b4136f9f3e54e4662c3bc1bd1d84a42a9a0f0c1e9" checksum = "c42f3f41a2de00b01c0aaad383c5a45241efc8b2d1eda5661812fda5f3cdcff5"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
@ -884,7 +874,7 @@ version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5a5fbabedabe362c618c714dbefda9927b5afc8e2a8102f47f081089a9019226" checksum = "5a5fbabedabe362c618c714dbefda9927b5afc8e2a8102f47f081089a9019226"
dependencies = [ dependencies = [
"itertools 0.12.1", "itertools",
"unicode-width", "unicode-width",
] ]

View file

@ -52,9 +52,9 @@ crossterm = "0.27.0"
hashbrown = "0.14.5" hashbrown = "0.14.5"
notify-debouncer-mini = { version = "0.4.1", default-features = false } notify-debouncer-mini = { version = "0.4.1", default-features = false }
os_pipe = "1.2.0" os_pipe = "1.2.0"
ratatui = { version = "0.27.0", default-features = false, features = ["crossterm"] } ratatui = { version = "0.26.3", default-features = false, features = ["crossterm"] }
rustlings-macros = { path = "rustlings-macros", version = "=6.0.0-beta.9" } rustlings-macros = { path = "rustlings-macros", version = "=6.0.0-beta.9" }
serde_json = "1.0.118" serde_json = "1.0.117"
serde.workspace = true serde.workspace = true
toml_edit.workspace = true toml_edit.workspace = true

View file

@ -1,9 +1,12 @@
// This function returns how much icecream there is left in the fridge. // This function returns how much icecream there is left in the fridge.
// If it's before 22:00 (24-hour system), then 5 scoops are left. At 22:00, // If it's before 10PM, there's 5 scoops left. At 10PM, someone eats it
// someone eats it all, so no icecream is left (value 0). Return `None` if // all, so there'll be no more left :(
// `hour_of_day` is higher than 23. fn maybe_icecream(time_of_day: u16) -> Option<u16> {
fn maybe_icecream(hour_of_day: u16) -> Option<u16> { // We use the 24-hour system here, so 10PM is a value of 22 and 12AM is a
// TODO: Complete the function body. // value of 0. The Option output should gracefully handle cases where
// time_of_day > 23.
// TODO: Complete the function body - remember to return an Option!
???
} }
fn main() { fn main() {
@ -14,14 +17,6 @@ fn main() {
mod tests { mod tests {
use super::*; use super::*;
#[test]
fn raw_value() {
// TODO: Fix this test. How do you get the value contained in the
// Option?
let icecreams = maybe_icecream(12);
assert_eq!(icecreams, 5);
}
#[test] #[test]
fn check_icecream() { fn check_icecream() {
assert_eq!(maybe_icecream(0), Some(5)); assert_eq!(maybe_icecream(0), Some(5));
@ -29,7 +24,14 @@ mod tests {
assert_eq!(maybe_icecream(18), Some(5)); assert_eq!(maybe_icecream(18), Some(5));
assert_eq!(maybe_icecream(22), Some(0)); assert_eq!(maybe_icecream(22), Some(0));
assert_eq!(maybe_icecream(23), Some(0)); assert_eq!(maybe_icecream(23), Some(0));
assert_eq!(maybe_icecream(24), None);
assert_eq!(maybe_icecream(25), None); assert_eq!(maybe_icecream(25), None);
} }
#[test]
fn raw_value() {
// TODO: Fix this test. How do you get at the value contained in the
// Option?
let icecreams = maybe_icecream(12);
assert_eq!(icecreams, 5);
}
} }

View file

@ -9,7 +9,7 @@ mod tests {
let target = "rustlings"; let target = "rustlings";
let optional_target = Some(target); let optional_target = Some(target);
// TODO: Make this an if-let statement whose value is `Some`. // TODO: Make this an if let statement whose value is "Some" type
word = optional_target { word = optional_target {
assert_eq!(word, target); assert_eq!(word, target);
} }
@ -20,15 +20,15 @@ mod tests {
let range = 10; let range = 10;
let mut optional_integers: Vec<Option<i8>> = vec![None]; let mut optional_integers: Vec<Option<i8>> = vec![None];
for i in 1..=range { for i in 1..(range + 1) {
optional_integers.push(Some(i)); optional_integers.push(Some(i));
} }
let mut cursor = range; let mut cursor = range;
// TODO: Make this a while-let statement. Remember that `Vec::pop()` // TODO: make this a while let statement - remember that vector.pop also
// adds another layer of `Option`. You can do nested pattern matching // adds another layer of Option<T>. You can stack `Option<T>`s into
// in if-let and while-let statements. // while let and if let.
integer = optional_integers.pop() { integer = optional_integers.pop() {
assert_eq!(integer, cursor); assert_eq!(integer, cursor);
cursor -= 1; cursor -= 1;

View file

@ -1,17 +1,14 @@
#[derive(Debug)]
struct Point { struct Point {
x: i32, x: i32,
y: i32, y: i32,
} }
fn main() { fn main() {
let optional_point = Some(Point { x: 100, y: 200 }); let y: Option<Point> = Some(Point { x: 100, y: 200 });
// TODO: Fix the compiler error by adding something to this match statement. match y {
match optional_point { Some(p) => println!("Co-ordinates are {},{} ", p.x, p.y),
Some(p) => println!("Co-ordinates are {},{}", p.x, p.y), _ => panic!("no match!"),
_ => panic!("No match!"),
} }
y; // Fix without deleting this line.
println!("{optional_point:?}"); // Don't change this line.
} }

View file

@ -1,22 +1,22 @@
// TODO: This function refuses to generate text to be printed on a nametag if // This function refuses to generate text to be printed on a nametag if you pass
// you pass it an empty string. It'd be nicer if it explained what the problem // it an empty string. It'd be nicer if it explained what the problem was,
// was instead of just returning `None`. Thankfully, Rust has a similar // instead of just sometimes returning `None`. Thankfully, Rust has a similar
// construct to `Option` that can be used to express error conditions. Change // construct to `Option` that can be used to express error conditions. Let's use
// the function signature and body to return `Result<String, String>` instead // it!
// of `Option<String>`.
fn main() {
// You can optionally experiment here.
}
fn generate_nametag_text(name: String) -> Option<String> { fn generate_nametag_text(name: String) -> Option<String> {
if name.is_empty() { if name.is_empty() {
// Empty names aren't allowed. // Empty names aren't allowed.
None None
} else { } else {
Some(format!("Hi! My name is {name}")) Some(format!("Hi! My name is {}", name))
} }
} }
fn main() {
// You can optionally experiment here.
}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
@ -24,18 +24,17 @@ mod tests {
#[test] #[test]
fn generates_nametag_text_for_a_nonempty_name() { fn generates_nametag_text_for_a_nonempty_name() {
assert_eq!( assert_eq!(
generate_nametag_text("Beyoncé".to_string()).as_deref(), generate_nametag_text("Beyoncé".into()),
Ok("Hi! My name is Beyoncé"), Ok("Hi! My name is Beyoncé".into())
); );
} }
#[test] #[test]
fn explains_why_generating_nametag_text_fails() { fn explains_why_generating_nametag_text_fails() {
assert_eq!( assert_eq!(
generate_nametag_text(String::new()) generate_nametag_text("".into()),
.as_ref() // Don't change this line
.map_err(|e| e.as_str()), Err("`name` was empty; it must be nonempty.".into())
Err("Empty names aren't allowed"),
); );
} }
} }

View file

@ -2,16 +2,16 @@
// 5 tokens, and whenever you purchase items there is a processing fee of 1 // 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 // 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 `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 // the player typed in the quantity, though, we get it as a string-- and they
// typed anything, not just numbers! // might have typed anything, not just numbers!
// //
// Right now, this function isn't handling the error case at all (and isn't // 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 // handling the success case properly either). What we want to do is: if we call
// the `total_cost` function on a string that is not a number, that function // 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 // will return a `ParseIntError`, and in that case, we want to immediately
// that error from our function and not try to multiply and add. // 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 // There are at least two ways to implement this that are both correct-- but one
// is a lot shorter! // is a lot shorter!
use std::num::ParseIntError; use std::num::ParseIntError;
@ -19,8 +19,6 @@ use std::num::ParseIntError;
fn total_cost(item_quantity: &str) -> Result<i32, ParseIntError> { fn total_cost(item_quantity: &str) -> Result<i32, ParseIntError> {
let processing_fee = 1; let processing_fee = 1;
let cost_per_item = 5; let cost_per_item = 5;
// 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) Ok(qty * cost_per_item + processing_fee)
@ -33,7 +31,6 @@ fn main() {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
use std::num::IntErrorKind;
#[test] #[test]
fn item_quantity_is_a_valid_number() { fn item_quantity_is_a_valid_number() {
@ -43,8 +40,8 @@ mod tests {
#[test] #[test]
fn item_quantity_is_an_invalid_number() { fn item_quantity_is_an_invalid_number() {
assert_eq!( assert_eq!(
total_cost("beep boop").unwrap_err().kind(), total_cost("beep boop").unwrap_err().to_string(),
&IntErrorKind::InvalidDigit, "invalid digit found in string"
); );
} }
} }

View file

@ -4,17 +4,6 @@
use std::num::ParseIntError; 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)
}
// TODO: Fix the compiler error by changing the signature and body of the
// `main` function.
fn main() { fn main() {
let mut tokens = 100; let mut tokens = 100;
let pretend_user_input = "8"; let pretend_user_input = "8";
@ -25,6 +14,14 @@ fn main() {
println!("You can't afford that many!"); println!("You can't afford that many!");
} else { } else {
tokens -= cost; tokens -= cost;
println!("You now have {tokens} tokens."); println!("You now have {} tokens.", tokens);
} }
} }
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)
}

View file

@ -1,16 +1,16 @@
#[derive(PartialEq, Debug)]
struct PositiveNonzeroInteger(u64);
#[derive(PartialEq, Debug)] #[derive(PartialEq, Debug)]
enum CreationError { enum CreationError {
Negative, Negative,
Zero, Zero,
} }
#[derive(PartialEq, Debug)]
struct PositiveNonzeroInteger(u64);
impl PositiveNonzeroInteger { impl PositiveNonzeroInteger {
fn new(value: i64) -> Result<Self, CreationError> { fn new(value: i64) -> Result<PositiveNonzeroInteger, CreationError> {
// TODO: This function shouldn't always return an `Ok`. // Hmm... Why is this always returning an Ok value?
Ok(Self(value as u64)) Ok(PositiveNonzeroInteger(value as u64))
} }
} }
@ -24,14 +24,11 @@ mod tests {
#[test] #[test]
fn test_creation() { fn test_creation() {
assert!(PositiveNonzeroInteger::new(10).is_ok());
assert_eq!( assert_eq!(
PositiveNonzeroInteger::new(10),
Ok(PositiveNonzeroInteger(10)),
);
assert_eq!(
PositiveNonzeroInteger::new(-10),
Err(CreationError::Negative), Err(CreationError::Negative),
PositiveNonzeroInteger::new(-10)
); );
assert_eq!(PositiveNonzeroInteger::new(0), Err(CreationError::Zero)); assert_eq!(Err(CreationError::Zero), PositiveNonzeroInteger::new(0));
} }
} }

View file

@ -1,18 +1,38 @@
// This exercise is an altered version of the `errors4` exercise. It uses some // This program uses an altered version of the code from errors4.
// 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 // This exercise uses some concepts that we won't get to until later in the
// you can read ahead if you like. For now, think of the `Box<dyn ???>` type as // course, like `Box` and the `From` trait. It's not important to understand
// an "I want anything that does ???" type. // 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 // 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 // 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. To do so, The Box is declared as of type Box<dyn Trait> where Trait is
// `Trait` is the trait the compiler looks for on any value used in that // the trait the compiler looks for on any value used in that context. For this
// context. For this exercise, that context is the potential errors which // exercise, that context is the potential errors which can be returned in a
// can be returned in a `Result`. // Result.
//
// What can we use to describe both errors? In other words, is there a trait
// which both errors implement?
use std::error::Error; use std::error;
use std::fmt; use std::fmt;
use std::num::ParseIntError;
// TODO: update the return type of `main()` to make this compile.
fn main() -> Result<(), Box<dyn ???>> {
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)] #[derive(PartialEq, Debug)]
enum CreationError { enum CreationError {
@ -20,7 +40,17 @@ enum CreationError {
Zero, Zero,
} }
// This is required so that `CreationError` can implement `Error`. 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 { impl fmt::Display for CreationError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let description = match *self { let description = match *self {
@ -31,26 +61,4 @@ impl fmt::Display for CreationError {
} }
} }
impl Error for CreationError {} impl error::Error for CreationError {}
#[derive(PartialEq, Debug)]
struct PositiveNonzeroInteger(u64);
impl PositiveNonzeroInteger {
fn new(value: i64) -> Result<PositiveNonzeroInteger, CreationError> {
match value {
0 => Err(CreationError::Zero),
x if x < 0 => Err(CreationError::Negative),
x => Ok(PositiveNonzeroInteger(x as u64)),
}
}
}
// 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?
fn main() {
let pretend_user_input = "42";
let x: i64 = pretend_user_input.parse()?;
println!("output={:?}", PositiveNonzeroInteger::new(x)?);
Ok(())
}

View file

@ -603,7 +603,7 @@ hint = """
Options can have a `Some` value, with an inner value, or a `None` value, Options can have a `Some` value, with an inner value, or a `None` value,
without an inner value. without an inner value.
There are multiple ways to get at the inner value, you can use `unwrap`, or There's multiple ways to get at the inner value, you can use `unwrap`, or
pattern match. Unwrapping is the easiest, but how do you do it safely so that pattern match. Unwrapping is the easiest, but how do you do it safely so that
it doesn't panic in your face later?""" it doesn't panic in your face later?"""
@ -616,9 +616,9 @@ Check out:
- https://doc.rust-lang.org/rust-by-example/flow_control/if_let.html - https://doc.rust-lang.org/rust-by-example/flow_control/if_let.html
- https://doc.rust-lang.org/rust-by-example/flow_control/while_let.html - https://doc.rust-lang.org/rust-by-example/flow_control/while_let.html
Remember that `Option`s can be nested in if-let and while-let statements. Remember that `Option`s can be stacked in `if let` and `while let`.
For example: `if let Some(Some(x)) = y` For example: `Some(Some(variable)) = variable2`
Also see `Option::flatten` Also see `Option::flatten`
""" """
@ -631,8 +631,7 @@ hint = """
The compiler says a partial move happened in the `match` statement. How can The compiler says a partial move happened in the `match` statement. How can
this be avoided? The compiler shows the correction needed. this be avoided? The compiler shows the correction needed.
After making the correction as suggested by the compiler, read the related docs After making the correction as suggested by the compiler, do read:
page:
https://doc.rust-lang.org/std/keyword.ref.html""" https://doc.rust-lang.org/std/keyword.ref.html"""
# ERROR HANDLING # ERROR HANDLING
@ -647,8 +646,8 @@ is that `generate_nametag_text` should return a `Result` instead of an `Option`.
To make this change, you'll need to: To make this change, you'll need to:
- update the return type in the function signature to be a `Result<String, - update the return type in the function signature to be a `Result<String,
String>` that could be the variants `Ok(String)` and `Err(String)` String>` that could be the variants `Ok(String)` and `Err(String)`
- change the body of the function to return `Ok()` where it currently - change the body of the function to return `Ok(stuff)` where it currently
returns `Some()` returns `Some(stuff)`
- change the body of the function to return `Err(error message)` where it - change the body of the function to return `Err(error message)` where it
currently returns `None`""" currently returns `None`"""
@ -660,11 +659,12 @@ One way to handle this is using a `match` statement on
`item_quantity.parse::<i32>()` where the cases are `Ok(something)` and `item_quantity.parse::<i32>()` where the cases are `Ok(something)` and
`Err(something)`. `Err(something)`.
This pattern is very common in Rust, though, so there's the `?` operator that This pattern is very common in Rust, though, so there's a `?` operator that
does pretty much what you would make that match statement do for you! does pretty much what you would make that match statement do for you!
Take a look at this section of the "Error Handling" chapter: Take a look at this section of the 'Error Handling' chapter:
https://doc.rust-lang.org/book/ch09-02-recoverable-errors-with-result.html#a-shortcut-for-propagating-errors-the--operator""" https://doc.rust-lang.org/book/ch09-02-recoverable-errors-with-result.html#a-shortcut-for-propagating-errors-the--operator
and give it a try!"""
[[exercises]] [[exercises]]
name = "errors3" name = "errors3"
@ -675,40 +675,43 @@ If other functions can return a `Result`, why shouldn't `main`? It's a fairly
common convention to return something like `Result<(), ErrorType>` from your common convention to return something like `Result<(), ErrorType>` from your
`main` function. `main` function.
The unit type `()` is there because nothing is really needed in terms of a The unit (`()`) type is there because nothing is really needed in terms of
positive result.""" positive results."""
[[exercises]] [[exercises]]
name = "errors4" name = "errors4"
dir = "13_error_handling" dir = "13_error_handling"
hint = """ hint = """
`PositiveNonzeroInteger::new` is always creating a new instance and returning `PositiveNonzeroInteger::new` is always creating a new instance and returning
an `Ok` result. But it should be doing some checking, returning an `Err` if an `Ok` result.
those checks fail, and only returning an `Ok` if those checks determine that
everything is okay :)""" It should be doing some checking, returning an `Err` result if those checks
fail, and only returning an `Ok` result if those checks determine that
everything is... okay :)"""
[[exercises]] [[exercises]]
name = "errors5" name = "errors5"
dir = "13_error_handling" dir = "13_error_handling"
test = false test = false
hint = """ hint = """
There are two different possible `Result` types produced within the `main` There are two different possible `Result` types produced within `main()`, which
function, which are propagated using the `?` operators. How do we declare a are propagated using `?` operators. How do we declare a return type from
return type for the `main` function that allows both? `main()` that allows both?
Under the hood, the `?` operator calls `From::from` on the error value to Under the hood, the `?` operator calls `From::from` on the error value to
convert it to a boxed trait object, a `Box<dyn Error>`. This boxed trait object convert it to a boxed trait object, a `Box<dyn error::Error>`. This boxed trait
is polymorphic, and since all errors implement the `Error` trait, we can capture object is polymorphic, and since all errors implement the `error::Error` trait,
lots of different errors in one `Box` object. we can capture lots of different errors in one "Box" object.
Check out this section of The Book: Check out this section of the book:
https://doc.rust-lang.org/book/ch09-02-recoverable-errors-with-result.html#a-shortcut-for-propagating-errors-the--operator https://doc.rust-lang.org/book/ch09-02-recoverable-errors-with-result.html#a-shortcut-for-propagating-errors-the--operator
Read more about boxing errors: Read more about boxing errors:
https://doc.rust-lang.org/stable/rust-by-example/error/multiple_error_types/boxing_errors.html https://doc.rust-lang.org/stable/rust-by-example/error/multiple_error_types/boxing_errors.html
Read more about using the `?` operator with boxed errors: Read more about using the `?` operator with boxed errors:
https://doc.rust-lang.org/stable/rust-by-example/error/multiple_error_types/reenter_question_mark.html""" https://doc.rust-lang.org/stable/rust-by-example/error/multiple_error_types/reenter_question_mark.html
"""
[[exercises]] [[exercises]]
name = "errors6" name = "errors6"

View file

@ -1,38 +1 @@
// This function returns how much icecream there is left in the fridge. // Solutions will be available before the stable release. Thank you for testing the beta version 🥰
// If it's before 22:00 (24-hour system), then 5 scoops are left. At 22:00,
// someone eats it all, so no icecream is left (value 0). Return `None` if
// `hour_of_day` is higher than 23.
fn maybe_icecream(hour_of_day: u16) -> Option<u16> {
match hour_of_day {
0..22 => Some(5),
22..24 => Some(0),
_ => None,
}
}
fn main() {
// You can optionally experiment here.
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn raw_value() {
// Using `unwrap` is fine in a test.
let icecreams = maybe_icecream(12).unwrap();
assert_eq!(icecreams, 5);
}
#[test]
fn check_icecream() {
assert_eq!(maybe_icecream(0), Some(5));
assert_eq!(maybe_icecream(9), Some(5));
assert_eq!(maybe_icecream(18), Some(5));
assert_eq!(maybe_icecream(22), Some(0));
assert_eq!(maybe_icecream(23), Some(0));
assert_eq!(maybe_icecream(24), None);
assert_eq!(maybe_icecream(25), None);
}
}

View file

@ -1,37 +1 @@
fn main() { // Solutions will be available before the stable release. Thank you for testing the beta version 🥰
// You can optionally experiment here.
}
#[cfg(test)]
mod tests {
#[test]
fn simple_option() {
let target = "rustlings";
let optional_target = Some(target);
// if-let
if let Some(word) = optional_target {
assert_eq!(word, target);
}
}
#[test]
fn layered_option() {
let range = 10;
let mut optional_integers: Vec<Option<i8>> = vec![None];
for i in 1..=range {
optional_integers.push(Some(i));
}
let mut cursor = range;
// while-let with nested pattern matching
while let Some(Some(integer)) = optional_integers.pop() {
assert_eq!(integer, cursor);
cursor -= 1;
}
assert_eq!(cursor, 0);
}
}

View file

@ -1,26 +1 @@
#[derive(Debug)] // Solutions will be available before the stable release. Thank you for testing the beta version 🥰
struct Point {
x: i32,
y: i32,
}
fn main() {
let optional_point = Some(Point { x: 100, y: 200 });
// Solution 1: Matching over the `Option` (not `&Option`) but without moving
// out of the `Some` variant.
match optional_point {
Some(ref p) => println!("Co-ordinates are {},{}", p.x, p.y),
// ^^^ added
_ => panic!("No match!"),
}
// Solution 2: Matching over a reference (`&Option`) by added `&` before
// `optional_point`.
match &optional_point {
Some(p) => println!("Co-ordinates are {},{}", p.x, p.y),
_ => panic!("No match!"),
}
println!("{optional_point:?}");
}

View file

@ -1,37 +1 @@
fn generate_nametag_text(name: String) -> Result<String, String> { // Solutions will be available before the stable release. Thank you for testing the beta version 🥰
// ^^^^^^ ^^^^^^
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,57 +1 @@
// Say we're writing a game where you can buy items with tokens. All items cost // Solutions will be available before the stable release. Thank you for testing the beta version 🥰
// 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 (and isn't
// handling the success case properly either). 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;
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,32 +1 @@
// This is a program that is trying to use a completed version of the // Solutions will be available before the stable release. Thank you for testing the beta version 🥰
// `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(())
}

View file

@ -1,42 +1 @@
#[derive(PartialEq, Debug)] // Solutions will be available before the stable release. Thank you for testing the beta version 🥰
enum CreationError {
Negative,
Zero,
}
#[derive(PartialEq, Debug)]
struct PositiveNonzeroInteger(u64);
impl PositiveNonzeroInteger {
fn new(value: i64) -> Result<Self, CreationError> {
if value == 0 {
Err(CreationError::Zero)
} else if value < 0 {
Err(CreationError::Negative)
} else {
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,54 +1 @@
// This exercise is an altered version of the `errors4` exercise. It uses some // Solutions will be available before the stable release. Thank you for testing the beta version 🥰
// 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(())
}