From 27860807eb7aef8d7d990ae8284278892c9a5d15 Mon Sep 17 00:00:00 2001 From: BraCR10 Date: Sun, 17 Aug 2025 22:42:50 -0600 Subject: [PATCH] 23th completed --- .rustlings-state.txt | 23 ++- exercises/19_smart_pointers/arc1.rs | 4 +- exercises/19_smart_pointers/box1.rs | 6 +- exercises/19_smart_pointers/cow1.rs | 6 +- exercises/19_smart_pointers/rc1.rs | 11 +- exercises/20_threads/threads1.rs | 6 + exercises/20_threads/threads2.rs | 9 +- exercises/20_threads/threads3.rs | 8 +- exercises/21_macros/macros1.rs | 2 +- exercises/21_macros/macros2.rs | 10 +- exercises/21_macros/macros3.rs | 1 + exercises/21_macros/macros4.rs | 2 +- exercises/22_clippy/clippy1.rs | 3 +- exercises/22_clippy/clippy2.rs | 2 +- exercises/22_clippy/clippy3.rs | 11 +- exercises/23_conversions/as_ref_mut.rs | 8 +- exercises/23_conversions/from_into.rs | 22 ++- exercises/23_conversions/from_str.rs | 22 ++- exercises/23_conversions/try_from_into.rs | 41 ++++- exercises/23_conversions/using_as.rs | 2 +- exercises/README.md | 1 + exercises/image.png | Bin 0 -> 17420 bytes solutions/19_smart_pointers/arc1.rs | 45 ++++- solutions/19_smart_pointers/box1.rs | 49 +++++- solutions/19_smart_pointers/cow1.rs | 71 +++++++- solutions/19_smart_pointers/rc1.rs | 106 +++++++++++- solutions/20_threads/threads1.rs | 37 +++- solutions/20_threads/threads2.rs | 43 ++++- solutions/20_threads/threads3.rs | 64 ++++++- solutions/21_macros/macros1.rs | 12 +- solutions/21_macros/macros2.rs | 12 +- solutions/21_macros/macros3.rs | 15 +- solutions/21_macros/macros4.rs | 17 +- solutions/22_clippy/clippy1.rs | 17 +- solutions/22_clippy/clippy2.rs | 10 +- solutions/22_clippy/clippy3.rs | 31 +++- solutions/23_conversions/as_ref_mut.rs | 62 ++++++- solutions/23_conversions/from_into.rs | 138 ++++++++++++++- solutions/23_conversions/from_str.rs | 119 ++++++++++++- solutions/23_conversions/try_from_into.rs | 195 +++++++++++++++++++++- solutions/23_conversions/using_as.rs | 26 ++- 41 files changed, 1176 insertions(+), 93 deletions(-) create mode 100644 exercises/image.png diff --git a/.rustlings-state.txt b/.rustlings-state.txt index 56f1a680..f80a4ed7 100644 --- a/.rustlings-state.txt +++ b/.rustlings-state.txt @@ -1,6 +1,6 @@ DON'T EDIT THIS FILE! -box1 +as_ref_mut intro1 intro2 @@ -76,4 +76,23 @@ iterators1 iterators2 iterators3 iterators4 -iterators5 \ No newline at end of file +iterators5 +box1 +rc1 +arc1 +cow1 +threads1 +threads2 +threads3 +macros1 +macros2 +macros3 +macros4 +clippy1 +clippy2 +clippy3 +using_as +from_into +from_str +try_from_into +as_ref_mut \ No newline at end of file diff --git a/exercises/19_smart_pointers/arc1.rs b/exercises/19_smart_pointers/arc1.rs index 6bb860f9..3b196439 100644 --- a/exercises/19_smart_pointers/arc1.rs +++ b/exercises/19_smart_pointers/arc1.rs @@ -23,13 +23,13 @@ fn main() { let numbers: Vec<_> = (0..100u32).collect(); // TODO: Define `shared_numbers` by using `Arc`. - // let shared_numbers = ???; + let shared_numbers = Arc::new(numbers); let mut join_handles = Vec::new(); for offset in 0..8 { // TODO: Define `child_numbers` using `shared_numbers`. - // let child_numbers = ???; + let child_numbers = Arc::clone(&shared_numbers); let handle = thread::spawn(move || { let sum: u32 = child_numbers.iter().filter(|&&n| n % 8 == offset).sum(); diff --git a/exercises/19_smart_pointers/box1.rs b/exercises/19_smart_pointers/box1.rs index d70e1c3d..ee379cae 100644 --- a/exercises/19_smart_pointers/box1.rs +++ b/exercises/19_smart_pointers/box1.rs @@ -12,18 +12,18 @@ // TODO: Use a `Box` in the enum definition to make the code compile. #[derive(PartialEq, Debug)] enum List { - Cons(i32, List), + Cons(i32, Box), Nil, } // TODO: Create an empty cons list. fn create_empty_list() -> List { - todo!() + List::Nil } // TODO: Create a non-empty cons list. fn create_non_empty_list() -> List { - todo!() + List::Cons(1, Box::new(List::Nil)) } fn main() { diff --git a/exercises/19_smart_pointers/cow1.rs b/exercises/19_smart_pointers/cow1.rs index 15665007..eb8ebaae 100644 --- a/exercises/19_smart_pointers/cow1.rs +++ b/exercises/19_smart_pointers/cow1.rs @@ -39,7 +39,7 @@ mod tests { let mut input = Cow::from(&vec); abs_all(&mut input); // TODO: Replace `todo!()` with `Cow::Owned(_)` or `Cow::Borrowed(_)`. - assert!(matches!(input, todo!())); + assert!(matches!(input,Cow::Borrowed(_))); } #[test] @@ -52,7 +52,7 @@ mod tests { let mut input = Cow::from(vec); abs_all(&mut input); // TODO: Replace `todo!()` with `Cow::Owned(_)` or `Cow::Borrowed(_)`. - assert!(matches!(input, todo!())); + assert!(matches!(input, Cow::Owned(_))); } #[test] @@ -64,6 +64,6 @@ mod tests { let mut input = Cow::from(vec); abs_all(&mut input); // TODO: Replace `todo!()` with `Cow::Owned(_)` or `Cow::Borrowed(_)`. - assert!(matches!(input, todo!())); + assert!(matches!(input,Cow::Owned(_))); } } diff --git a/exercises/19_smart_pointers/rc1.rs b/exercises/19_smart_pointers/rc1.rs index ecd34387..203884a0 100644 --- a/exercises/19_smart_pointers/rc1.rs +++ b/exercises/19_smart_pointers/rc1.rs @@ -60,17 +60,17 @@ mod tests { jupiter.details(); // TODO - let saturn = Planet::Saturn(Rc::new(Sun)); + let saturn = Planet::Saturn(Rc::clone(&sun)); println!("reference count = {}", Rc::strong_count(&sun)); // 7 references saturn.details(); // TODO - let uranus = Planet::Uranus(Rc::new(Sun)); + let uranus = Planet::Uranus(Rc::clone(&sun)); println!("reference count = {}", Rc::strong_count(&sun)); // 8 references uranus.details(); // TODO - let neptune = Planet::Neptune(Rc::new(Sun)); + let neptune = Planet::Neptune(Rc::clone(&sun)); println!("reference count = {}", Rc::strong_count(&sun)); // 9 references neptune.details(); @@ -92,12 +92,17 @@ mod tests { println!("reference count = {}", Rc::strong_count(&sun)); // 4 references // TODO + drop(mercury); + println!("reference count = {}", Rc::strong_count(&sun)); // 3 references // TODO + drop(venus); + println!("reference count = {}", Rc::strong_count(&sun)); // 2 references // TODO + drop(earth); println!("reference count = {}", Rc::strong_count(&sun)); // 1 reference assert_eq!(Rc::strong_count(&sun), 1); diff --git a/exercises/20_threads/threads1.rs b/exercises/20_threads/threads1.rs index dbc64b16..63280ad4 100644 --- a/exercises/20_threads/threads1.rs +++ b/exercises/20_threads/threads1.rs @@ -24,6 +24,12 @@ fn main() { for handle in handles { // TODO: Collect the results of all threads into the `results` vector. // Use the `JoinHandle` struct which is returned by `thread::spawn`. + + match handle.join() { + Ok(val) => results.push(val), + Err(_)=>results.push(0) + } + } if results.len() != 10 { diff --git a/exercises/20_threads/threads2.rs b/exercises/20_threads/threads2.rs index 7020cb9c..6f8fd96e 100644 --- a/exercises/20_threads/threads2.rs +++ b/exercises/20_threads/threads2.rs @@ -2,7 +2,7 @@ // work. But this time, the spawned threads need to be in charge of updating a // shared value: `JobStatus.jobs_done` -use std::{sync::Arc, thread, time::Duration}; +use std::{sync::{Arc, Mutex}, thread, time::Duration}; struct JobStatus { jobs_done: u32, @@ -10,7 +10,7 @@ struct JobStatus { fn main() { // TODO: `Arc` isn't enough if you want a **mutable** shared state. - let status = Arc::new(JobStatus { jobs_done: 0 }); + let status = Arc::new(Mutex::new(JobStatus { jobs_done: 0 })); let mut handles = Vec::new(); for _ in 0..10 { @@ -19,7 +19,8 @@ fn main() { thread::sleep(Duration::from_millis(250)); // TODO: You must take an action before you update a shared value. - status_shared.jobs_done += 1; + let mut job_status = status_shared.lock().unwrap(); + job_status.jobs_done += 1; }); handles.push(handle); } @@ -30,5 +31,5 @@ fn main() { } // TODO: Print the value of `JobStatus.jobs_done`. - println!("Jobs done: {}", todo!()); + println!("Jobs done: {}", status.lock().unwrap().jobs_done); } diff --git a/exercises/20_threads/threads3.rs b/exercises/20_threads/threads3.rs index 6d16bd9f..b72dc26d 100644 --- a/exercises/20_threads/threads3.rs +++ b/exercises/20_threads/threads3.rs @@ -17,21 +17,25 @@ impl Queue { fn send_tx(q: Queue, tx: mpsc::Sender) { // TODO: We want to send `tx` to both threads. But currently, it is moved // into the first thread. How could you solve this problem? + + let tx1=tx.clone(); thread::spawn(move || { for val in q.first_half { println!("Sending {val:?}"); - tx.send(val).unwrap(); + tx1.send(val).unwrap(); thread::sleep(Duration::from_millis(250)); } }); + let tx2=tx.clone(); thread::spawn(move || { for val in q.second_half { println!("Sending {val:?}"); - tx.send(val).unwrap(); + tx2.send(val).unwrap(); thread::sleep(Duration::from_millis(250)); } }); + } fn main() { diff --git a/exercises/21_macros/macros1.rs b/exercises/21_macros/macros1.rs index fb3c3ff9..fb550f9d 100644 --- a/exercises/21_macros/macros1.rs +++ b/exercises/21_macros/macros1.rs @@ -6,5 +6,5 @@ macro_rules! my_macro { fn main() { // TODO: Fix the macro call. - my_macro(); + my_macro!(); } diff --git a/exercises/21_macros/macros2.rs b/exercises/21_macros/macros2.rs index 2d9dec76..a56d137f 100644 --- a/exercises/21_macros/macros2.rs +++ b/exercises/21_macros/macros2.rs @@ -1,10 +1,12 @@ -fn main() { - my_macro!(); -} - // TODO: Fix the compiler error by moving the whole definition of this macro. macro_rules! my_macro { () => { println!("Check out my macro!"); }; } + +fn main() { + my_macro!(); +} + + diff --git a/exercises/21_macros/macros3.rs b/exercises/21_macros/macros3.rs index 95374948..acda10b4 100644 --- a/exercises/21_macros/macros3.rs +++ b/exercises/21_macros/macros3.rs @@ -1,5 +1,6 @@ // TODO: Fix the compiler error without taking the macro definition out of this // module. +#[macro_use] mod macros { macro_rules! my_macro { () => { diff --git a/exercises/21_macros/macros4.rs b/exercises/21_macros/macros4.rs index 9d77f6a5..3396f0d3 100644 --- a/exercises/21_macros/macros4.rs +++ b/exercises/21_macros/macros4.rs @@ -3,7 +3,7 @@ macro_rules! my_macro { () => { println!("Check out my macro!"); - } + }; ($val:expr) => { println!("Look at this other macro: {}", $val); } diff --git a/exercises/22_clippy/clippy1.rs b/exercises/22_clippy/clippy1.rs index 7165da45..ebd63bf2 100644 --- a/exercises/22_clippy/clippy1.rs +++ b/exercises/22_clippy/clippy1.rs @@ -3,10 +3,11 @@ // // For these exercises, the code will fail to compile when there are Clippy // warnings. Check Clippy's suggestions from the output to solve the exercise. +use std::f32::consts::PI; fn main() { // TODO: Fix the Clippy lint in this line. - let pi = 3.14; + let pi = PI; let radius: f32 = 5.0; let area = pi * radius.powi(2); diff --git a/exercises/22_clippy/clippy2.rs b/exercises/22_clippy/clippy2.rs index 8cfe6f80..fb12f983 100644 --- a/exercises/22_clippy/clippy2.rs +++ b/exercises/22_clippy/clippy2.rs @@ -2,7 +2,7 @@ fn main() { let mut res = 42; let option = Some(12); // TODO: Fix the Clippy lint. - for x in option { + if let Some(x)= option { res += x; } diff --git a/exercises/22_clippy/clippy3.rs b/exercises/22_clippy/clippy3.rs index 4f788349..9f527be5 100644 --- a/exercises/22_clippy/clippy3.rs +++ b/exercises/22_clippy/clippy3.rs @@ -4,24 +4,25 @@ #[rustfmt::skip] #[allow(unused_variables, unused_assignments)] fn main() { - let my_option: Option<()> = None; + let my_option: Option<()> = Some(()); if my_option.is_none() { - println!("{:?}", my_option.unwrap()); + println!("{my_option :?}"); } let my_arr = &[ - -1, -2, -3 + -1, -2, -3, -4, -5, -6 ]; println!("My array! Here it is: {my_arr:?}"); - let my_empty_vec = vec![1, 2, 3, 4, 5].resize(0, 5); + let my_empty_vec: () = vec![1, 2, 3, 4, 5].resize(0, 5); println!("This Vec is empty, see? {my_empty_vec:?}"); let mut value_a = 45; let mut value_b = 66; + let pivot=value_b; // Let's swap these two! value_a = value_b; - value_b = value_a; + value_b = pivot; println!("value a: {value_a}; value b: {value_b}"); } diff --git a/exercises/23_conversions/as_ref_mut.rs b/exercises/23_conversions/as_ref_mut.rs index d7892dd4..b0157ecf 100644 --- a/exercises/23_conversions/as_ref_mut.rs +++ b/exercises/23_conversions/as_ref_mut.rs @@ -5,20 +5,22 @@ // Obtain the number of bytes (not characters) in the given argument // (`.len()` returns the number of bytes in a string). // TODO: Add the `AsRef` trait appropriately as a trait bound. -fn byte_counter(arg: T) -> usize { +fn byte_counter>(arg: T) -> usize { arg.as_ref().len() } // Obtain the number of characters (not bytes) in the given argument. // TODO: Add the `AsRef` trait appropriately as a trait bound. -fn char_counter(arg: T) -> usize { +fn char_counter>(arg: T) -> usize { arg.as_ref().chars().count() } // Squares a number using `as_mut()`. // TODO: Add the appropriate trait bound. -fn num_sq(arg: &mut T) { +fn num_sq>(arg: &mut T) { // TODO: Implement the function body. + let v = arg.as_mut(); + *v=v.pow(2); } fn main() { diff --git a/exercises/23_conversions/from_into.rs b/exercises/23_conversions/from_into.rs index bc2783a3..a0bdbcc6 100644 --- a/exercises/23_conversions/from_into.rs +++ b/exercises/23_conversions/from_into.rs @@ -34,7 +34,27 @@ impl Default for Person { // 5. Parse the second element from the split operation into a `u8` as the age. // 6. If parsing the age fails, return the default of `Person`. impl From<&str> for Person { - fn from(s: &str) -> Self {} + fn from(s: &str) -> Self { + let split: Vec<&str> = s.split(",").collect(); + + if split.len() != 2 { + return Self::default(); + } + let name = split[0]; + let age_str = split[1]; + + if name.is_empty() { + return Self::default(); + } + + match age_str.parse::() { + Ok(age) => Person { + name: name.to_string(), + age, + }, + Err(_) => Self::default(), + } + } } fn main() { diff --git a/exercises/23_conversions/from_str.rs b/exercises/23_conversions/from_str.rs index ec6d3fd1..9d3f3a67 100644 --- a/exercises/23_conversions/from_str.rs +++ b/exercises/23_conversions/from_str.rs @@ -41,7 +41,27 @@ enum ParsePersonError { impl FromStr for Person { type Err = ParsePersonError; - fn from_str(s: &str) -> Result {} + fn from_str(s: &str) -> Result { + let split: Vec<&str> = s.split(",").collect(); + + if split.len() != 2 { + return Err(ParsePersonError::BadLen); + } + let name = split[0]; + let age_str = split[1]; + + if name.is_empty() { + return Err(ParsePersonError::NoName); + } + + match age_str.parse::() { + Ok(age) => Ok(Person { + name: name.to_string(), + age, + }), + Err(e) => Err(ParsePersonError::ParseInt(e)), + } + } } fn main() { diff --git a/exercises/23_conversions/try_from_into.rs b/exercises/23_conversions/try_from_into.rs index f3ae80a9..c0b7f474 100644 --- a/exercises/23_conversions/try_from_into.rs +++ b/exercises/23_conversions/try_from_into.rs @@ -28,14 +28,35 @@ enum IntoColorError { impl TryFrom<(i16, i16, i16)> for Color { type Error = IntoColorError; - fn try_from(tuple: (i16, i16, i16)) -> Result {} + fn try_from(tuple: (i16, i16, i16)) -> Result { + let (a,b,c)=tuple; + if (0..=255).contains(&a) && (0..=255).contains(&b) && (0..=255).contains(&c) { + Ok(Color { + red: a as u8, + green: b as u8, + blue: c as u8, + }) + } else { + Err(IntoColorError::IntConversion) + } + } } // TODO: Array implementation. impl TryFrom<[i16; 3]> for Color { type Error = IntoColorError; - fn try_from(arr: [i16; 3]) -> Result {} + fn try_from(arr: [i16; 3]) -> Result { + if arr.iter().all(|num| (0..=255).contains(num)){ + Ok(Color { + red: arr[0] as u8, + green: arr[1] as u8, + blue: arr[2] as u8, + }) + } else { + Err(IntoColorError::IntConversion) + } + } } // TODO: Slice implementation. @@ -43,7 +64,21 @@ impl TryFrom<[i16; 3]> for Color { impl TryFrom<&[i16]> for Color { type Error = IntoColorError; - fn try_from(slice: &[i16]) -> Result {} + fn try_from(slice: &[i16]) -> Result { + if slice.len()!=3{ + return Err(IntoColorError::BadLen); + }; + + if slice.iter().all(|num| (0..=255).contains(num)) { + Ok(Color { + red: slice[0] as u8, + green: slice[1] as u8, + blue: slice[2] as u8, + }) + } else { + Err(IntoColorError::IntConversion) + } + } } fn main() { diff --git a/exercises/23_conversions/using_as.rs b/exercises/23_conversions/using_as.rs index c131d1f3..dff453b2 100644 --- a/exercises/23_conversions/using_as.rs +++ b/exercises/23_conversions/using_as.rs @@ -5,7 +5,7 @@ fn average(values: &[f64]) -> f64 { let total = values.iter().sum::(); // TODO: Make a conversion before dividing. - total / values.len() + total / values.len() as f64 } fn main() { diff --git a/exercises/README.md b/exercises/README.md index 237f2f1e..3dc99dc3 100644 --- a/exercises/README.md +++ b/exercises/README.md @@ -1,3 +1,4 @@ +![alt text](image.png) # Exercise to Book Chapter mapping | Exercise | Book Chapter | diff --git a/exercises/image.png b/exercises/image.png new file mode 100644 index 0000000000000000000000000000000000000000..ffd37092ee5bff7a62fff2575b3cec28510cf6af GIT binary patch literal 17420 zcmcJ1XH-+&)-HA|2#A1m0TmFX_mW6gI?`(dM7kJyj|Gq-M0zIz(o3ZGM5XuMLMK3g z&_hi~xcI)`x%YnGIrqmMU#+}Mqb>~_na*M)fA+e~C+_sL!;%ISHhZO+)fx;r|1 zbw{|O!QSZxPtaxZpgYg=`9rz$zdfh^UP#Uz>KbpM7;d=yF^fYnho^~~yiWBRwLE6% zBKhT*XXMwf2Yt8FP7li3dIQ!e>uo1IT$W(-7UZ0(%Xk zi)7kr^YvT-MrLGWUrWD*Mf)(fgUxi3JPn2^eKJ&taYeGC6lyEQ7m;xZNA z%;8$AqDPOZPDF!cqS|+B@3Q8ZA3ZDoF+rw1^^bdkK&c+U)h)t`O4fow}6O z(jKgy%g>NoXf!2nJs-jqKeyRinj>Nb1C4I(M0=T0nHrGPH2ySvD?>2w&_cP(AC1}d!hwAWMo?O7|Z(M>z;P1{e8C&v=eX)r)t%eiPU8I+Dnh`f=u>z>E^T$OrzQc znAU#88jG;YAzLnej}y@lvlXtX3r3}V*DacLboQ+|nrPSF^TcIUO?|E#O7&uG6y-l$ z1=Z}0yl9|%It%+g_P_;#8NTjJ~=TyTGT9No9nDN9j$Wf=!nnTF0AzN z(tNT-e{7d?58g*3p7Ej&Q+qmsm3hI4O-C;<#xOYqp8_ zU$M|7H|9LVVQpGMeqa6 z0uD7<^ALVkmfPW^ojxx1pxtdrFE95L)gpb{hv^{E;=ydl5=)}dX?xC*?z=|crVmt~ zW?z{ak~!S>7t@=qyB(pbGo@MS&@e=k(OzVChptgtuR`~L;(!@ zl%D@wXEIyjnAXB>7pkYpB&MMNS<#yDoVnC*=yPjHOA-Ntfq}Mg2M;E-h9Obo$*lVu zlKZs1+Y4B9WysJz=N!==(fRFzvlBjFBk_)xS}GmvYvLWIyq7aCdJcDwtgj0ujID`S zcL&G$Yo@xn;X3B$$1+)OMPE5$ntrsEXrnUJeX9^@SS({kqNQMxaCd_BRKfU~zGe&4 z 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 literal 0 HcmV?d00001 diff --git a/solutions/19_smart_pointers/arc1.rs b/solutions/19_smart_pointers/arc1.rs index dcf2377a..bd76189f 100644 --- a/solutions/19_smart_pointers/arc1.rs +++ b/solutions/19_smart_pointers/arc1.rs @@ -1,4 +1,45 @@ +// In this exercise, we are given a `Vec` of `u32` called `numbers` with values +// ranging from 0 to 99. We would like to use this set of numbers within 8 +// different threads simultaneously. Each thread is going to get the sum of +// every eighth value with an offset. +// +// The first thread (offset 0), will sum 0, 8, 16, … +// The second thread (offset 1), will sum 1, 9, 17, … +// The third thread (offset 2), will sum 2, 10, 18, … +// … +// The eighth thread (offset 7), will sum 7, 15, 23, … +// +// Each thread should own a reference-counting pointer to the vector of +// numbers. But `Rc` isn't thread-safe. Therefore, we need to use `Arc`. +// +// Don't get distracted by how threads are spawned and joined. We will practice +// that later in the exercises about threads. + +// Don't change the lines below. +#![forbid(unused_imports)] +use std::{sync::Arc, thread}; + fn main() { - // DON'T EDIT THIS SOLUTION FILE! - // It will be automatically filled after you finish the exercise. + let numbers: Vec<_> = (0..100u32).collect(); + + let shared_numbers = Arc::new(numbers); + // ^^^^^^^^^^^^^^^^^ + + let mut join_handles = Vec::new(); + + for offset in 0..8 { + let child_numbers = Arc::clone(&shared_numbers); + // ^^^^^^^^^^^^^^^^^^^^^^^^^^^ + + let handle = thread::spawn(move || { + let sum: u32 = child_numbers.iter().filter(|&&n| n % 8 == offset).sum(); + println!("Sum of offset {offset} is {sum}"); + }); + + join_handles.push(handle); + } + + for handle in join_handles.into_iter() { + handle.join().unwrap(); + } } diff --git a/solutions/19_smart_pointers/box1.rs b/solutions/19_smart_pointers/box1.rs index dcf2377a..189cc562 100644 --- a/solutions/19_smart_pointers/box1.rs +++ b/solutions/19_smart_pointers/box1.rs @@ -1,4 +1,47 @@ -fn main() { - // DON'T EDIT THIS SOLUTION FILE! - // It will be automatically filled after you finish the exercise. +// At compile time, Rust needs to know how much space a type takes up. This +// becomes problematic for recursive types, where a value can have as part of +// itself another value of the same type. To get around the issue, we can use a +// `Box` - a smart pointer used to store data on the heap, which also allows us +// to wrap a recursive type. +// +// The recursive type we're implementing in this exercise is the "cons list", a +// data structure frequently found in functional programming languages. Each +// item in a cons list contains two elements: The value of the current item and +// the next item. The last item is a value called `Nil`. + +#[derive(PartialEq, Debug)] +enum List { + Cons(i32, Box), + Nil, +} + +fn create_empty_list() -> List { + List::Nil +} + +fn create_non_empty_list() -> List { + List::Cons(42, Box::new(List::Nil)) +} + +fn main() { + println!("This is an empty cons list: {:?}", create_empty_list()); + println!( + "This is a non-empty cons list: {:?}", + create_non_empty_list(), + ); +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_create_empty_list() { + assert_eq!(create_empty_list(), List::Nil); + } + + #[test] + fn test_create_non_empty_list() { + assert_ne!(create_empty_list(), create_non_empty_list()); + } } diff --git a/solutions/19_smart_pointers/cow1.rs b/solutions/19_smart_pointers/cow1.rs index dcf2377a..461143be 100644 --- a/solutions/19_smart_pointers/cow1.rs +++ b/solutions/19_smart_pointers/cow1.rs @@ -1,4 +1,69 @@ -fn main() { - // DON'T EDIT THIS SOLUTION FILE! - // It will be automatically filled after you finish the exercise. +// This exercise explores the `Cow` (Clone-On-Write) smart pointer. It can +// enclose and provide immutable access to borrowed data and clone the data +// lazily when mutation or ownership is required. The type is designed to work +// with general borrowed data via the `Borrow` trait. + +use std::borrow::Cow; + +fn abs_all(input: &mut Cow<[i32]>) { + for ind in 0..input.len() { + let value = input[ind]; + if value < 0 { + // Clones into a vector if not already owned. + input.to_mut()[ind] = -value; + } + } +} + +fn main() { + // You can optionally experiment here. +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn reference_mutation() { + // Clone occurs because `input` needs to be mutated. + let vec = vec![-1, 0, 1]; + let mut input = Cow::from(&vec); + abs_all(&mut input); + assert!(matches!(input, Cow::Owned(_))); + } + + #[test] + fn reference_no_mutation() { + // No clone occurs because `input` doesn't need to be mutated. + let vec = vec![0, 1, 2]; + let mut input = Cow::from(&vec); + abs_all(&mut input); + assert!(matches!(input, Cow::Borrowed(_))); + // ^^^^^^^^^^^^^^^^ + } + + #[test] + fn owned_no_mutation() { + // We can also pass `vec` without `&` so `Cow` owns it directly. In this + // case, no mutation occurs (all numbers are already absolute) and thus + // also no clone. But the result is still owned because it was never + // borrowed or mutated. + let vec = vec![0, 1, 2]; + let mut input = Cow::from(vec); + abs_all(&mut input); + assert!(matches!(input, Cow::Owned(_))); + // ^^^^^^^^^^^^^ + } + + #[test] + fn owned_mutation() { + // Of course this is also the case if a mutation does occur (not all + // numbers are absolute). In this case, the call to `to_mut()` in the + // `abs_all` function returns a reference to the same data as before. + let vec = vec![-1, 0, 1]; + let mut input = Cow::from(vec); + abs_all(&mut input); + assert!(matches!(input, Cow::Owned(_))); + // ^^^^^^^^^^^^^ + } } diff --git a/solutions/19_smart_pointers/rc1.rs b/solutions/19_smart_pointers/rc1.rs index dcf2377a..c0a41abf 100644 --- a/solutions/19_smart_pointers/rc1.rs +++ b/solutions/19_smart_pointers/rc1.rs @@ -1,4 +1,104 @@ -fn main() { - // DON'T EDIT THIS SOLUTION FILE! - // It will be automatically filled after you finish the exercise. +// In this exercise, we want to express the concept of multiple owners via the +// `Rc` type. This is a model of our solar system - there is a `Sun` type and +// multiple `Planet`s. The planets take ownership of the sun, indicating that +// they revolve around the sun. + +use std::rc::Rc; + +#[derive(Debug)] +struct Sun; + +#[derive(Debug)] +enum Planet { + Mercury(Rc), + Venus(Rc), + Earth(Rc), + Mars(Rc), + Jupiter(Rc), + Saturn(Rc), + Uranus(Rc), + Neptune(Rc), +} + +impl Planet { + fn details(&self) { + println!("Hi from {self:?}!"); + } +} + +fn main() { + // You can optionally experiment here. +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn rc1() { + let sun = Rc::new(Sun); + println!("reference count = {}", Rc::strong_count(&sun)); // 1 reference + + let mercury = Planet::Mercury(Rc::clone(&sun)); + println!("reference count = {}", Rc::strong_count(&sun)); // 2 references + mercury.details(); + + let venus = Planet::Venus(Rc::clone(&sun)); + println!("reference count = {}", Rc::strong_count(&sun)); // 3 references + venus.details(); + + let earth = Planet::Earth(Rc::clone(&sun)); + println!("reference count = {}", Rc::strong_count(&sun)); // 4 references + earth.details(); + + let mars = Planet::Mars(Rc::clone(&sun)); + println!("reference count = {}", Rc::strong_count(&sun)); // 5 references + mars.details(); + + let jupiter = Planet::Jupiter(Rc::clone(&sun)); + println!("reference count = {}", Rc::strong_count(&sun)); // 6 references + jupiter.details(); + + let saturn = Planet::Saturn(Rc::clone(&sun)); + println!("reference count = {}", Rc::strong_count(&sun)); // 7 references + saturn.details(); + + // TODO + let uranus = Planet::Uranus(Rc::clone(&sun)); + println!("reference count = {}", Rc::strong_count(&sun)); // 8 references + uranus.details(); + + // TODO + let neptune = Planet::Neptune(Rc::clone(&sun)); + println!("reference count = {}", Rc::strong_count(&sun)); // 9 references + neptune.details(); + + assert_eq!(Rc::strong_count(&sun), 9); + + drop(neptune); + println!("reference count = {}", Rc::strong_count(&sun)); // 8 references + + drop(uranus); + println!("reference count = {}", Rc::strong_count(&sun)); // 7 references + + drop(saturn); + println!("reference count = {}", Rc::strong_count(&sun)); // 6 references + + drop(jupiter); + println!("reference count = {}", Rc::strong_count(&sun)); // 5 references + + drop(mars); + println!("reference count = {}", Rc::strong_count(&sun)); // 4 references + + drop(earth); + println!("reference count = {}", Rc::strong_count(&sun)); // 3 references + + drop(venus); + println!("reference count = {}", Rc::strong_count(&sun)); // 2 references + + drop(mercury); + println!("reference count = {}", Rc::strong_count(&sun)); // 1 reference + + assert_eq!(Rc::strong_count(&sun), 1); + } } diff --git a/solutions/20_threads/threads1.rs b/solutions/20_threads/threads1.rs index dcf2377a..1fc5bc9c 100644 --- a/solutions/20_threads/threads1.rs +++ b/solutions/20_threads/threads1.rs @@ -1,4 +1,37 @@ +// This program spawns multiple threads that each runs for at least 250ms, and +// each thread returns how much time it took to complete. The program should +// wait until all the spawned threads have finished and should collect their +// return values into a vector. + +use std::{ + thread, + time::{Duration, Instant}, +}; + fn main() { - // DON'T EDIT THIS SOLUTION FILE! - // It will be automatically filled after you finish the exercise. + let mut handles = Vec::new(); + for i in 0..10 { + let handle = thread::spawn(move || { + let start = Instant::now(); + thread::sleep(Duration::from_millis(250)); + println!("Thread {i} done"); + start.elapsed().as_millis() + }); + handles.push(handle); + } + + let mut results = Vec::new(); + for handle in handles { + // Collect the results of all threads into the `results` vector. + results.push(handle.join().unwrap()); + } + + if results.len() != 10 { + panic!("Oh no! Some thread isn't done yet!"); + } + + println!(); + for (i, result) in results.into_iter().enumerate() { + println!("Thread {i} took {result}ms"); + } } diff --git a/solutions/20_threads/threads2.rs b/solutions/20_threads/threads2.rs index dcf2377a..bc268d63 100644 --- a/solutions/20_threads/threads2.rs +++ b/solutions/20_threads/threads2.rs @@ -1,4 +1,41 @@ -fn main() { - // DON'T EDIT THIS SOLUTION FILE! - // It will be automatically filled after you finish the exercise. +// Building on the last exercise, we want all of the threads to complete their +// work. But this time, the spawned threads need to be in charge of updating a +// shared value: `JobStatus.jobs_done` + +use std::{ + sync::{Arc, Mutex}, + thread, + time::Duration, +}; + +struct JobStatus { + jobs_done: u32, +} + +fn main() { + // `Arc` isn't enough if you want a **mutable** shared state. + // We need to wrap the value with a `Mutex`. + let status = Arc::new(Mutex::new(JobStatus { jobs_done: 0 })); + // ^^^^^^^^^^^ ^ + + let mut handles = Vec::new(); + for _ in 0..10 { + let status_shared = Arc::clone(&status); + let handle = thread::spawn(move || { + thread::sleep(Duration::from_millis(250)); + + // Lock before you update a shared value. + status_shared.lock().unwrap().jobs_done += 1; + // ^^^^^^^^^^^^^^^^ + }); + handles.push(handle); + } + + // Waiting for all jobs to complete. + for handle in handles { + handle.join().unwrap(); + } + + println!("Jobs done: {}", status.lock().unwrap().jobs_done); + // ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ } diff --git a/solutions/20_threads/threads3.rs b/solutions/20_threads/threads3.rs index dcf2377a..7ceefea0 100644 --- a/solutions/20_threads/threads3.rs +++ b/solutions/20_threads/threads3.rs @@ -1,4 +1,62 @@ -fn main() { - // DON'T EDIT THIS SOLUTION FILE! - // It will be automatically filled after you finish the exercise. +use std::{sync::mpsc, thread, time::Duration}; + +struct Queue { + first_half: Vec, + second_half: Vec, +} + +impl Queue { + fn new() -> Self { + Self { + first_half: vec![1, 2, 3, 4, 5], + second_half: vec![6, 7, 8, 9, 10], + } + } +} + +fn send_tx(q: Queue, tx: mpsc::Sender) { + // Clone the sender `tx` first. + let tx_clone = tx.clone(); + thread::spawn(move || { + for val in q.first_half { + println!("Sending {val:?}"); + // Then use the clone in the first thread. This means that + // `tx_clone` is moved to the first thread and `tx` to the second. + tx_clone.send(val).unwrap(); + thread::sleep(Duration::from_millis(250)); + } + }); + + thread::spawn(move || { + for val in q.second_half { + println!("Sending {val:?}"); + tx.send(val).unwrap(); + thread::sleep(Duration::from_millis(250)); + } + }); +} + +fn main() { + // You can optionally experiment here. +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn threads3() { + let (tx, rx) = mpsc::channel(); + let queue = Queue::new(); + + send_tx(queue, tx); + + let mut received = Vec::with_capacity(10); + for value in rx { + received.push(value); + } + + received.sort(); + assert_eq!(received, [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]); + } } diff --git a/solutions/21_macros/macros1.rs b/solutions/21_macros/macros1.rs index dcf2377a..1b861564 100644 --- a/solutions/21_macros/macros1.rs +++ b/solutions/21_macros/macros1.rs @@ -1,4 +1,10 @@ -fn main() { - // DON'T EDIT THIS SOLUTION FILE! - // It will be automatically filled after you finish the exercise. +macro_rules! my_macro { + () => { + println!("Check out my macro!"); + }; +} + +fn main() { + my_macro!(); + // ^ } diff --git a/solutions/21_macros/macros2.rs b/solutions/21_macros/macros2.rs index dcf2377a..b6fd5d2c 100644 --- a/solutions/21_macros/macros2.rs +++ b/solutions/21_macros/macros2.rs @@ -1,4 +1,10 @@ -fn main() { - // DON'T EDIT THIS SOLUTION FILE! - // It will be automatically filled after you finish the exercise. +// Moved the macro definition to be before its call. +macro_rules! my_macro { + () => { + println!("Check out my macro!"); + }; +} + +fn main() { + my_macro!(); } diff --git a/solutions/21_macros/macros3.rs b/solutions/21_macros/macros3.rs index dcf2377a..df35be4d 100644 --- a/solutions/21_macros/macros3.rs +++ b/solutions/21_macros/macros3.rs @@ -1,4 +1,13 @@ -fn main() { - // DON'T EDIT THIS SOLUTION FILE! - // It will be automatically filled after you finish the exercise. +// Added the attribute `macro_use` attribute. +#[macro_use] +mod macros { + macro_rules! my_macro { + () => { + println!("Check out my macro!"); + }; + } +} + +fn main() { + my_macro!(); } diff --git a/solutions/21_macros/macros4.rs b/solutions/21_macros/macros4.rs index dcf2377a..41bcad16 100644 --- a/solutions/21_macros/macros4.rs +++ b/solutions/21_macros/macros4.rs @@ -1,4 +1,15 @@ -fn main() { - // DON'T EDIT THIS SOLUTION FILE! - // It will be automatically filled after you finish the exercise. +// Added semicolons to separate the macro arms. +#[rustfmt::skip] +macro_rules! my_macro { + () => { + println!("Check out my macro!"); + }; + ($val:expr) => { + println!("Look at this other macro: {}", $val); + }; +} + +fn main() { + my_macro!(); + my_macro!(7777); } diff --git a/solutions/22_clippy/clippy1.rs b/solutions/22_clippy/clippy1.rs index dcf2377a..b9d1ec17 100644 --- a/solutions/22_clippy/clippy1.rs +++ b/solutions/22_clippy/clippy1.rs @@ -1,4 +1,17 @@ +// The Clippy tool is a collection of lints to analyze your code so you can +// catch common mistakes and improve your Rust code. +// +// For these exercises, the code will fail to compile when there are Clippy +// warnings. Check Clippy's suggestions from the output to solve the exercise. + +use std::f32::consts::PI; + fn main() { - // DON'T EDIT THIS SOLUTION FILE! - // It will be automatically filled after you finish the exercise. + // Use the more accurate `PI` constant. + let pi = PI; + let radius: f32 = 5.0; + + let area = pi * radius.powi(2); + + println!("The area of a circle with radius {radius:.2} is {area:.5}"); } diff --git a/solutions/22_clippy/clippy2.rs b/solutions/22_clippy/clippy2.rs index dcf2377a..7f635628 100644 --- a/solutions/22_clippy/clippy2.rs +++ b/solutions/22_clippy/clippy2.rs @@ -1,4 +1,10 @@ fn main() { - // DON'T EDIT THIS SOLUTION FILE! - // It will be automatically filled after you finish the exercise. + let mut res = 42; + let option = Some(12); + // Use `if-let` instead of iteration. + if let Some(x) = option { + res += x; + } + + println!("{res}"); } diff --git a/solutions/22_clippy/clippy3.rs b/solutions/22_clippy/clippy3.rs index dcf2377a..811d1847 100644 --- a/solutions/22_clippy/clippy3.rs +++ b/solutions/22_clippy/clippy3.rs @@ -1,4 +1,31 @@ +use std::mem; + +#[rustfmt::skip] +#[allow(unused_variables, unused_assignments)] fn main() { - // DON'T EDIT THIS SOLUTION FILE! - // It will be automatically filled after you finish the exercise. + let my_option: Option<()> = None; + // `unwrap` of an `Option` after checking if it is `None` will panic. + // Use `if-let` instead. + if let Some(value) = my_option { + println!("{value:?}"); + } + + // A comma was missing. + let my_arr = &[ + -1, -2, -3, + -4, -5, -6, + ]; + println!("My array! Here it is: {:?}", my_arr); + + let mut my_empty_vec = vec![1, 2, 3, 4, 5]; + // `resize` mutates a vector instead of returning a new one. + // `resize(0, …)` clears a vector, so it is better to use `clear`. + my_empty_vec.clear(); + println!("This Vec is empty, see? {my_empty_vec:?}"); + + let mut value_a = 45; + let mut value_b = 66; + // Use `mem::swap` to correctly swap two values. + mem::swap(&mut value_a, &mut value_b); + println!("value a: {}; value b: {}", value_a, value_b); } diff --git a/solutions/23_conversions/as_ref_mut.rs b/solutions/23_conversions/as_ref_mut.rs index dcf2377a..a5d2d4fa 100644 --- a/solutions/23_conversions/as_ref_mut.rs +++ b/solutions/23_conversions/as_ref_mut.rs @@ -1,4 +1,60 @@ -fn main() { - // DON'T EDIT THIS SOLUTION FILE! - // It will be automatically filled after you finish the exercise. +// AsRef and AsMut allow for cheap reference-to-reference conversions. Read more +// about them at https://doc.rust-lang.org/std/convert/trait.AsRef.html and +// https://doc.rust-lang.org/std/convert/trait.AsMut.html, respectively. + +// Obtain the number of bytes (not characters) in the given argument +// (`.len()` returns the number of bytes in a string). +fn byte_counter>(arg: T) -> usize { + arg.as_ref().len() +} + +// Obtain the number of characters (not bytes) in the given argument. +fn char_counter>(arg: T) -> usize { + arg.as_ref().chars().count() +} + +// Squares a number using `as_mut()`. +fn num_sq>(arg: &mut T) { + let arg = arg.as_mut(); + *arg *= *arg; +} + +fn main() { + // You can optionally experiment here. +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn different_counts() { + let s = "Café au lait"; + assert_ne!(char_counter(s), byte_counter(s)); + } + + #[test] + fn same_counts() { + let s = "Cafe au lait"; + assert_eq!(char_counter(s), byte_counter(s)); + } + + #[test] + fn different_counts_using_string() { + let s = String::from("Café au lait"); + assert_ne!(char_counter(s.clone()), byte_counter(s)); + } + + #[test] + fn same_counts_using_string() { + let s = String::from("Cafe au lait"); + assert_eq!(char_counter(s.clone()), byte_counter(s)); + } + + #[test] + fn mut_box() { + let mut num: Box = Box::new(3); + num_sq(&mut num); + assert_eq!(*num, 9); + } } diff --git a/solutions/23_conversions/from_into.rs b/solutions/23_conversions/from_into.rs index dcf2377a..cec23cb4 100644 --- a/solutions/23_conversions/from_into.rs +++ b/solutions/23_conversions/from_into.rs @@ -1,4 +1,136 @@ -fn main() { - // DON'T EDIT THIS SOLUTION FILE! - // It will be automatically filled after you finish the exercise. +// The `From` trait is used for value-to-value conversions. If `From` is +// implemented, an implementation of `Into` is automatically provided. +// You can read more about it in the documentation: +// https://doc.rust-lang.org/std/convert/trait.From.html + +#[derive(Debug)] +struct Person { + name: String, + age: u8, +} + +// We implement the Default trait to use it as a fallback when the provided +// string is not convertible into a `Person` object. +impl Default for Person { + fn default() -> Self { + Self { + name: String::from("John"), + age: 30, + } + } +} + +impl From<&str> for Person { + fn from(s: &str) -> Self { + let mut split = s.split(','); + let (Some(name), Some(age), None) = (split.next(), split.next(), split.next()) else { + // ^^^^ there should be no third element + return Self::default(); + }; + + if name.is_empty() { + return Self::default(); + } + + let Ok(age) = age.parse() else { + return Self::default(); + }; + + Self { + name: name.into(), + age, + } + } +} + +fn main() { + // Use the `from` function. + let p1 = Person::from("Mark,20"); + println!("{p1:?}"); + + // Since `From` is implemented for Person, we are able to use `Into`. + let p2: Person = "Gerald,70".into(); + println!("{p2:?}"); +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_default() { + let dp = Person::default(); + assert_eq!(dp.name, "John"); + assert_eq!(dp.age, 30); + } + + #[test] + fn test_bad_convert() { + let p = Person::from(""); + assert_eq!(p.name, "John"); + assert_eq!(p.age, 30); + } + + #[test] + fn test_good_convert() { + let p = Person::from("Mark,20"); + assert_eq!(p.name, "Mark"); + assert_eq!(p.age, 20); + } + + #[test] + fn test_bad_age() { + let p = Person::from("Mark,twenty"); + assert_eq!(p.name, "John"); + assert_eq!(p.age, 30); + } + + #[test] + fn test_missing_comma_and_age() { + let p: Person = Person::from("Mark"); + assert_eq!(p.name, "John"); + assert_eq!(p.age, 30); + } + + #[test] + fn test_missing_age() { + let p: Person = Person::from("Mark,"); + assert_eq!(p.name, "John"); + assert_eq!(p.age, 30); + } + + #[test] + fn test_missing_name() { + let p: Person = Person::from(",1"); + assert_eq!(p.name, "John"); + assert_eq!(p.age, 30); + } + + #[test] + fn test_missing_name_and_age() { + let p: Person = Person::from(","); + assert_eq!(p.name, "John"); + assert_eq!(p.age, 30); + } + + #[test] + fn test_missing_name_and_invalid_age() { + let p: Person = Person::from(",one"); + assert_eq!(p.name, "John"); + assert_eq!(p.age, 30); + } + + #[test] + fn test_trailing_comma() { + let p: Person = Person::from("Mike,32,"); + assert_eq!(p.name, "John"); + assert_eq!(p.age, 30); + } + + #[test] + fn test_trailing_comma_and_some_string() { + let p: Person = Person::from("Mike,32,dog"); + assert_eq!(p.name, "John"); + assert_eq!(p.age, 30); + } } diff --git a/solutions/23_conversions/from_str.rs b/solutions/23_conversions/from_str.rs index dcf2377a..005b5012 100644 --- a/solutions/23_conversions/from_str.rs +++ b/solutions/23_conversions/from_str.rs @@ -1,4 +1,117 @@ -fn main() { - // DON'T EDIT THIS SOLUTION FILE! - // It will be automatically filled after you finish the exercise. +// This is similar to the previous `from_into` exercise. But this time, we'll +// implement `FromStr` and return errors instead of falling back to a default +// value. Additionally, upon implementing `FromStr`, you can use the `parse` +// method on strings to generate an object of the implementor type. You can read +// more about it in the documentation: +// https://doc.rust-lang.org/std/str/trait.FromStr.html + +use std::num::ParseIntError; +use std::str::FromStr; + +#[derive(Debug, PartialEq)] +struct Person { + name: String, + age: u8, +} + +// We will use this error type for the `FromStr` implementation. +#[derive(Debug, PartialEq)] +enum ParsePersonError { + // Incorrect number of fields + BadLen, + // Empty name field + NoName, + // Wrapped error from parse::() + ParseInt(ParseIntError), +} + +impl FromStr for Person { + type Err = ParsePersonError; + + fn from_str(s: &str) -> Result { + let mut split = s.split(','); + let (Some(name), Some(age), None) = (split.next(), split.next(), split.next()) else { + // ^^^^ there should be no third element + return Err(ParsePersonError::BadLen); + }; + + if name.is_empty() { + return Err(ParsePersonError::NoName); + } + + let age = age.parse().map_err(ParsePersonError::ParseInt)?; + + Ok(Self { + name: name.into(), + age, + }) + } +} + +fn main() { + let p = "Mark,20".parse::(); + println!("{p:?}"); +} + +#[cfg(test)] +mod tests { + use super::*; + use ParsePersonError::*; + + #[test] + fn empty_input() { + assert_eq!("".parse::(), Err(BadLen)); + } + + #[test] + fn good_input() { + let p = "John,32".parse::(); + assert!(p.is_ok()); + let p = p.unwrap(); + assert_eq!(p.name, "John"); + assert_eq!(p.age, 32); + } + + #[test] + fn missing_age() { + assert!(matches!("John,".parse::(), Err(ParseInt(_)))); + } + + #[test] + fn invalid_age() { + assert!(matches!("John,twenty".parse::(), Err(ParseInt(_)))); + } + + #[test] + fn missing_comma_and_age() { + assert_eq!("John".parse::(), Err(BadLen)); + } + + #[test] + fn missing_name() { + assert_eq!(",1".parse::(), Err(NoName)); + } + + #[test] + fn missing_name_and_age() { + assert!(matches!(",".parse::(), Err(NoName | ParseInt(_)))); + } + + #[test] + fn missing_name_and_invalid_age() { + assert!(matches!( + ",one".parse::(), + Err(NoName | ParseInt(_)), + )); + } + + #[test] + fn trailing_comma() { + assert_eq!("John,32,".parse::(), Err(BadLen)); + } + + #[test] + fn trailing_comma_and_some_string() { + assert_eq!("John,32,man".parse::(), Err(BadLen)); + } } diff --git a/solutions/23_conversions/try_from_into.rs b/solutions/23_conversions/try_from_into.rs index dcf2377a..ee802eb0 100644 --- a/solutions/23_conversions/try_from_into.rs +++ b/solutions/23_conversions/try_from_into.rs @@ -1,4 +1,193 @@ -fn main() { - // DON'T EDIT THIS SOLUTION FILE! - // It will be automatically filled after you finish the exercise. +// `TryFrom` is a simple and safe type conversion that may fail in a controlled +// way under some circumstances. Basically, this is the same as `From`. The main +// difference is that this should return a `Result` type instead of the target +// type itself. You can read more about it in the documentation: +// https://doc.rust-lang.org/std/convert/trait.TryFrom.html + +#![allow(clippy::useless_vec)] +use std::convert::{TryFrom, TryInto}; + +#[derive(Debug, PartialEq)] +struct Color { + red: u8, + green: u8, + blue: u8, +} + +// We will use this error type for the `TryFrom` conversions. +#[derive(Debug, PartialEq)] +enum IntoColorError { + // Incorrect length of slice + BadLen, + // Integer conversion error + IntConversion, +} + +impl TryFrom<(i16, i16, i16)> for Color { + type Error = IntoColorError; + + fn try_from(tuple: (i16, i16, i16)) -> Result { + let (Ok(red), Ok(green), Ok(blue)) = ( + u8::try_from(tuple.0), + u8::try_from(tuple.1), + u8::try_from(tuple.2), + ) else { + return Err(IntoColorError::IntConversion); + }; + + Ok(Self { red, green, blue }) + } +} + +impl TryFrom<[i16; 3]> for Color { + type Error = IntoColorError; + + fn try_from(arr: [i16; 3]) -> Result { + // Reuse the implementation for a tuple. + Self::try_from((arr[0], arr[1], arr[2])) + } +} + +impl TryFrom<&[i16]> for Color { + type Error = IntoColorError; + + fn try_from(slice: &[i16]) -> Result { + // Check the length. + if slice.len() != 3 { + return Err(IntoColorError::BadLen); + } + + // Reuse the implementation for a tuple. + Self::try_from((slice[0], slice[1], slice[2])) + } +} + +fn main() { + // Using the `try_from` function. + let c1 = Color::try_from((183, 65, 14)); + println!("{c1:?}"); + + // Since `TryFrom` is implemented for `Color`, we can use `TryInto`. + let c2: Result = [183, 65, 14].try_into(); + println!("{c2:?}"); + + let v = vec![183, 65, 14]; + // With slice we should use the `try_from` function + let c3 = Color::try_from(&v[..]); + println!("{c3:?}"); + // or put the slice within round brackets and use `try_into`. + let c4: Result = (&v[..]).try_into(); + println!("{c4:?}"); +} + +#[cfg(test)] +mod tests { + use super::*; + use IntoColorError::*; + + #[test] + fn test_tuple_out_of_range_positive() { + assert_eq!(Color::try_from((256, 1000, 10000)), Err(IntConversion)); + } + + #[test] + fn test_tuple_out_of_range_negative() { + assert_eq!(Color::try_from((-1, -10, -256)), Err(IntConversion)); + } + + #[test] + fn test_tuple_sum() { + assert_eq!(Color::try_from((-1, 255, 255)), Err(IntConversion)); + } + + #[test] + fn test_tuple_correct() { + let c: Result = (183, 65, 14).try_into(); + assert!(c.is_ok()); + assert_eq!( + c.unwrap(), + Color { + red: 183, + green: 65, + blue: 14, + } + ); + } + + #[test] + fn test_array_out_of_range_positive() { + let c: Result = [1000, 10000, 256].try_into(); + assert_eq!(c, Err(IntConversion)); + } + + #[test] + fn test_array_out_of_range_negative() { + let c: Result = [-10, -256, -1].try_into(); + assert_eq!(c, Err(IntConversion)); + } + + #[test] + fn test_array_sum() { + let c: Result = [-1, 255, 255].try_into(); + assert_eq!(c, Err(IntConversion)); + } + + #[test] + fn test_array_correct() { + let c: Result = [183, 65, 14].try_into(); + assert!(c.is_ok()); + assert_eq!( + c.unwrap(), + Color { + red: 183, + green: 65, + blue: 14 + } + ); + } + + #[test] + fn test_slice_out_of_range_positive() { + let arr = [10000, 256, 1000]; + assert_eq!(Color::try_from(&arr[..]), Err(IntConversion)); + } + + #[test] + fn test_slice_out_of_range_negative() { + let arr = [-256, -1, -10]; + assert_eq!(Color::try_from(&arr[..]), Err(IntConversion)); + } + + #[test] + fn test_slice_sum() { + let arr = [-1, 255, 255]; + assert_eq!(Color::try_from(&arr[..]), Err(IntConversion)); + } + + #[test] + fn test_slice_correct() { + let v = vec![183, 65, 14]; + let c: Result = Color::try_from(&v[..]); + assert!(c.is_ok()); + assert_eq!( + c.unwrap(), + Color { + red: 183, + green: 65, + blue: 14, + } + ); + } + + #[test] + fn test_slice_excess_length() { + let v = vec![0, 0, 0, 0]; + assert_eq!(Color::try_from(&v[..]), Err(BadLen)); + } + + #[test] + fn test_slice_insufficient_length() { + let v = vec![0, 0]; + assert_eq!(Color::try_from(&v[..]), Err(BadLen)); + } } diff --git a/solutions/23_conversions/using_as.rs b/solutions/23_conversions/using_as.rs index dcf2377a..14b92ebf 100644 --- a/solutions/23_conversions/using_as.rs +++ b/solutions/23_conversions/using_as.rs @@ -1,4 +1,24 @@ -fn main() { - // DON'T EDIT THIS SOLUTION FILE! - // It will be automatically filled after you finish the exercise. +// Type casting in Rust is done via the usage of the `as` operator. +// Note that the `as` operator is not only used when type casting. It also helps +// with renaming imports. + +fn average(values: &[f64]) -> f64 { + let total = values.iter().sum::(); + total / values.len() as f64 + // ^^^^^^ +} + +fn main() { + let values = [3.5, 0.3, 13.0, 11.7]; + println!("{}", average(&values)); +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn returns_proper_type_and_value() { + assert_eq!(average(&[3.5, 0.3, 13.0, 11.7]), 7.125); + } }