Compare commits

...

10 commits

Author SHA1 Message Date
mo8it 129884aff7 errors5 solution 2024-06-26 18:21:19 +02:00
mo8it 720b280bc1 Update deps 2024-06-26 16:59:13 +02:00
mo8it 9b7a5c041e errors4 solution 2024-06-26 15:54:18 +02:00
mo8it c46d8bdf95 errors3 solution 2024-06-26 15:44:33 +02:00
mo8it 050a23ce67 errors2 solution 2024-06-26 15:36:14 +02:00
mo8it 2afe6b38d3 Fix tests 2024-06-26 15:12:58 +02:00
mo8it 097f3c74ea errors1 solution 2024-06-26 15:06:29 +02:00
mo8it 25b5686dd2 options3 solution 2024-06-26 14:47:57 +02:00
mo8it a91888e79e option2 solution 2024-06-26 14:35:05 +02:00
mo8it c31e15c4cf options1 solution 2024-06-26 12:59:10 +02:00
19 changed files with 492 additions and 167 deletions

50
Cargo.lock generated
View file

@ -113,9 +113,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
[[package]] [[package]]
name = "bitflags" name = "bitflags"
version = "2.5.0" version = "2.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cf4b9d6a944f767f8e5e0db018570623c85f3d925ac718db4e06d0187adb21c1" checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de"
[[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.5.0", "bitflags 2.6.0",
"crossterm_winapi", "crossterm_winapi",
"libc", "libc",
"mio", "mio",
@ -262,9 +262,9 @@ checksum = "fea41bba32d969b513997752735605054bc0dfa92b4c56bf1189f2e174be7a10"
[[package]] [[package]]
name = "either" name = "either"
version = "1.12.0" version = "1.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3dca9240753cf90908d7e4aac30f630662b02aebaa1b58a3cadabdb23385b58b" checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0"
[[package]] [[package]]
name = "equivalent" name = "equivalent"
@ -363,6 +363,15 @@ 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"
@ -450,7 +459,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.5.0", "bitflags 2.6.0",
"crossbeam-channel", "crossbeam-channel",
"filetime", "filetime",
"fsevent-sys", "fsevent-sys",
@ -559,9 +568,9 @@ dependencies = [
[[package]] [[package]]
name = "proc-macro2" name = "proc-macro2"
version = "1.0.85" version = "1.0.86"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "22244ce15aa966053a896d1accb3a6e68469b97c7f33f284b99f0d576879fc23" checksum = "5e719e8df665df0d1c8fbfd238015744736151d4445ec0836b8e628aae103b77"
dependencies = [ dependencies = [
"unicode-ident", "unicode-ident",
] ]
@ -577,19 +586,20 @@ dependencies = [
[[package]] [[package]]
name = "ratatui" name = "ratatui"
version = "0.26.3" 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 = "f44c9e68fd46eda15c646fbb85e1040b657a58cdc8c98db1d97a55930d991eef" checksum = "d16546c5b5962abf8ce6e2881e722b4e0ae3b6f1a08a26ae3573c55853ca68d3"
dependencies = [ dependencies = [
"bitflags 2.5.0", "bitflags 2.6.0",
"cassowary", "cassowary",
"compact_str", "compact_str",
"crossterm", "crossterm",
"itertools", "itertools 0.13.0",
"lru", "lru",
"paste", "paste",
"stability", "stability",
"strum", "strum",
"strum_macros",
"unicode-segmentation", "unicode-segmentation",
"unicode-truncate", "unicode-truncate",
"unicode-width", "unicode-width",
@ -610,7 +620,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.5.0", "bitflags 2.6.0",
] ]
[[package]] [[package]]
@ -719,9 +729,9 @@ dependencies = [
[[package]] [[package]]
name = "serde_json" name = "serde_json"
version = "1.0.117" version = "1.0.118"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "455182ea6142b14f93f4bc5320a2b31c1f266b66a4a5c858b013302a5d8cbfc3" checksum = "d947f6b3163d8857ea16c4fa0dd4840d52f3041039a85decd46867eb1abef2e4"
dependencies = [ dependencies = [
"itoa", "itoa",
"ryu", "ryu",
@ -797,9 +807,9 @@ checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
[[package]] [[package]]
name = "strum" name = "strum"
version = "0.26.2" 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 = "5d8cec3501a5194c432b2b7976db6b7d10ec95c253208b45f83f7136aa985e29" checksum = "8fec0f0aef304996cf250b31b5a10dee7980c85da9d759361292b8bca5a18f06"
dependencies = [ dependencies = [
"strum_macros", "strum_macros",
] ]
@ -819,9 +829,9 @@ dependencies = [
[[package]] [[package]]
name = "syn" name = "syn"
version = "2.0.66" version = "2.0.68"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c42f3f41a2de00b01c0aaad383c5a45241efc8b2d1eda5661812fda5f3cdcff5" checksum = "901fa70d88b9d6c98022e23b4136f9f3e54e4662c3bc1bd1d84a42a9a0f0c1e9"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
@ -874,7 +884,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", "itertools 0.12.1",
"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.26.3", default-features = false, features = ["crossterm"] } ratatui = { version = "0.27.0", 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.117" serde_json = "1.0.118"
serde.workspace = true serde.workspace = true
toml_edit.workspace = true toml_edit.workspace = true

View file

@ -1,12 +1,9 @@
// 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 10PM, there's 5 scoops left. At 10PM, someone eats it // If it's before 22:00 (24-hour system), then 5 scoops are left. At 22:00,
// all, so there'll be no more left :( // someone eats it all, so no icecream is left (value 0). Return `None` if
fn maybe_icecream(time_of_day: u16) -> Option<u16> { // `hour_of_day` is higher than 23.
// We use the 24-hour system here, so 10PM is a value of 22 and 12AM is a fn maybe_icecream(hour_of_day: u16) -> Option<u16> {
// value of 0. The Option output should gracefully handle cases where // TODO: Complete the function body.
// time_of_day > 23.
// TODO: Complete the function body - remember to return an Option!
???
} }
fn main() { fn main() {
@ -17,6 +14,14 @@ 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));
@ -24,14 +29,7 @@ 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" type // TODO: Make this an if-let statement whose value is `Some`.
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 + 1) { for i in 1..=range {
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 vector.pop also // TODO: Make this a while-let statement. Remember that `Vec::pop()`
// adds another layer of Option<T>. You can stack `Option<T>`s into // adds another layer of `Option`. You can do nested pattern matching
// while let and if let. // in if-let and while-let statements.
integer = optional_integers.pop() { integer = optional_integers.pop() {
assert_eq!(integer, cursor); assert_eq!(integer, cursor);
cursor -= 1; cursor -= 1;

View file

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

View file

@ -1,22 +1,22 @@
// This function refuses to generate text to be printed on a nametag if you pass // TODO: This function refuses to generate text to be printed on a nametag if
// it an empty string. It'd be nicer if it explained what the problem was, // you pass it an empty string. It'd be nicer if it explained what the problem
// instead of just sometimes returning `None`. Thankfully, Rust has a similar // was instead of just returning `None`. Thankfully, Rust has a similar
// construct to `Option` that can be used to express error conditions. Let's use // construct to `Option` that can be used to express error conditions. Change
// it! // the function signature and body to return `Result<String, String>` instead
// 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,17 +24,18 @@ 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é".into()), generate_nametag_text("Beyoncé".to_string()).as_deref(),
Ok("Hi! My name is Beyoncé".into()) Ok("Hi! My name is Beyoncé"),
); );
} }
#[test] #[test]
fn explains_why_generating_nametag_text_fails() { fn explains_why_generating_nametag_text_fails() {
assert_eq!( assert_eq!(
generate_nametag_text("".into()), generate_nametag_text(String::new())
// Don't change this line .as_ref()
Err("`name` was empty; it must be nonempty.".into()) .map_err(|e| e.as_str()),
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, though, we get it as a string-- and they // the player typed in the quantity, we get it as a string. They might have
// might have typed anything, not just numbers! // 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`, and in that case, we want to immediately // will return a `ParseIntError`. In that case, we want to immediately return
// return that error from our function and not try to multiply and add. // 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,6 +19,8 @@ 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)
@ -31,6 +33,7 @@ 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() {
@ -40,8 +43,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().to_string(), total_cost("beep boop").unwrap_err().kind(),
"invalid digit found in string" &IntErrorKind::InvalidDigit,
); );
} }
} }

View file

@ -4,6 +4,17 @@
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";
@ -14,14 +25,6 @@ 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<PositiveNonzeroInteger, CreationError> { fn new(value: i64) -> Result<Self, CreationError> {
// Hmm... Why is this always returning an Ok value? // TODO: This function shouldn't always return an `Ok`.
Ok(PositiveNonzeroInteger(value as u64)) Ok(Self(value as u64))
} }
} }
@ -24,11 +24,14 @@ mod tests {
#[test] #[test]
fn test_creation() { fn test_creation() {
assert!(PositiveNonzeroInteger::new(10).is_ok());
assert_eq!( assert_eq!(
Err(CreationError::Negative), PositiveNonzeroInteger::new(10),
PositiveNonzeroInteger::new(-10) Ok(PositiveNonzeroInteger(10)),
); );
assert_eq!(Err(CreationError::Zero), PositiveNonzeroInteger::new(0)); assert_eq!(
PositiveNonzeroInteger::new(-10),
Err(CreationError::Negative),
);
assert_eq!(PositiveNonzeroInteger::new(0), Err(CreationError::Zero));
} }
} }

View file

@ -1,38 +1,18 @@
// This program uses an altered version of the code from errors4. // 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
// This exercise uses some concepts that we won't get to until later in the // `From` trait. It's not important to understand them in detail right now, but
// course, like `Box` and the `From` trait. It's not important to understand // you can read ahead if you like. For now, think of the `Box<dyn ???>` type as
// them in detail right now, but you can read ahead if you like. For now, think // an "I want anything that does ???" type.
// 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 is // trait. To do so, The `Box` is declared as of type `Box<dyn Trait>` where
// the trait the compiler looks for on any value used in that context. For this // `Trait` is the trait the compiler looks for on any value used in that
// exercise, that context is the potential errors which can be returned in a // context. For this exercise, that context is the potential errors which
// Result. // 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?
use std::error; use std::error::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 {
@ -40,17 +20,7 @@ enum CreationError {
Zero, Zero,
} }
impl PositiveNonzeroInteger { // This is required so that `CreationError` can implement `Error`.
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 {
@ -61,4 +31,26 @@ impl fmt::Display for CreationError {
} }
} }
impl error::Error for CreationError {} impl 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's multiple ways to get at the inner value, you can use `unwrap`, or There are 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 stacked in `if let` and `while let`. Remember that `Option`s can be nested in if-let and while-let statements.
For example: `Some(Some(variable)) = variable2` For example: `if let Some(Some(x)) = y`
Also see `Option::flatten` Also see `Option::flatten`
""" """
@ -631,7 +631,8 @@ 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, do read: After making the correction as suggested by the compiler, read the related docs
page:
https://doc.rust-lang.org/std/keyword.ref.html""" https://doc.rust-lang.org/std/keyword.ref.html"""
# ERROR HANDLING # ERROR HANDLING
@ -646,8 +647,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(stuff)` where it currently - change the body of the function to return `Ok()` where it currently
returns `Some(stuff)` returns `Some()`
- 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`"""
@ -659,12 +660,11 @@ 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 a `?` operator that This pattern is very common in Rust, though, so there's the `?` 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,43 +675,40 @@ 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 The unit type `()` is there because nothing is really needed in terms of a
positive results.""" positive result."""
[[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. an `Ok` result. But it should be doing some checking, returning an `Err` if
those checks fail, and only returning an `Ok` if those checks determine that
It should be doing some checking, returning an `Err` result if those checks everything is okay :)"""
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 `main()`, which There are two different possible `Result` types produced within the `main`
are propagated using `?` operators. How do we declare a return type from function, which are propagated using the `?` operators. How do we declare a
`main()` that allows both? return type for the `main` function 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::Error>`. This boxed trait convert it to a boxed trait object, a `Box<dyn Error>`. This boxed trait object
object is polymorphic, and since all errors implement the `error::Error` trait, is polymorphic, and since all errors implement the `Error` trait, we can capture
we can capture lots of different errors in one "Box" object. 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 +1,38 @@
// Solutions will be available before the stable release. Thank you for testing the beta version 🥰 // 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,
// 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 +1,37 @@
// Solutions will be available before the stable release. Thank you for testing the beta version 🥰 fn main() {
// 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 +1,26 @@
// Solutions will be available before the stable release. Thank you for testing the beta version 🥰 #[derive(Debug)]
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 +1,37 @@
// Solutions will be available before the stable release. Thank you for testing the beta version 🥰 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"),
);
}
}

View file

@ -1 +1,57 @@
// Solutions will be available before the stable release. Thank you for testing the beta version 🥰 // 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 (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 +1,32 @@
// Solutions will be available before the stable release. Thank you for testing the beta version 🥰 // 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(())
}

View file

@ -1 +1,42 @@
// Solutions will be available before the stable release. Thank you for testing the beta version 🥰 #[derive(PartialEq, Debug)]
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 +1,54 @@
// Solutions will be available before the stable release. Thank you for testing the beta version 🥰 // 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(())
}