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 00000000..ffd37092 Binary files /dev/null and b/exercises/image.png differ 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); + } }