diff options
| author | Adam Brewer <adamhb321@gmail.com> | 2023-10-16 07:37:12 -0400 |
|---|---|---|
| committer | Adam Brewer <adamhb321@gmail.com> | 2023-10-16 07:37:12 -0400 |
| commit | 64d95837e9813541cf5b357de13865ce687ae98d (patch) | |
| tree | f022c5d5ba01128811c0b77618a7adb843ee876b /exercises/23_conversions | |
| parent | c3941323e2c0b9ee286494327de92e00f23b9e3a (diff) | |
Update Exercises Directory Names to Reflect Order
Diffstat (limited to 'exercises/23_conversions')
| -rw-r--r-- | exercises/23_conversions/README.md | 23 | ||||
| -rw-r--r-- | exercises/23_conversions/as_ref_mut.rs | 65 | ||||
| -rw-r--r-- | exercises/23_conversions/from_into.rs | 140 | ||||
| -rw-r--r-- | exercises/23_conversions/from_str.rs | 133 | ||||
| -rw-r--r-- | exercises/23_conversions/try_from_into.rs | 193 | ||||
| -rw-r--r-- | exercises/23_conversions/using_as.rs | 33 |
6 files changed, 587 insertions, 0 deletions
diff --git a/exercises/23_conversions/README.md b/exercises/23_conversions/README.md new file mode 100644 index 0000000..619a78c --- /dev/null +++ b/exercises/23_conversions/README.md @@ -0,0 +1,23 @@ +# Type conversions + +Rust offers a multitude of ways to convert a value of a given type into another type. + +The simplest form of type conversion is a type cast expression. It is denoted with the binary operator `as`. For instance, `println!("{}", 1 + 1.0);` would not compile, since `1` is an integer while `1.0` is a float. However, `println!("{}", 1 as f32 + 1.0)` should compile. The exercise [`using_as`](using_as.rs) tries to cover this. + +Rust also offers traits that facilitate type conversions upon implementation. These traits can be found under the [`convert`](https://doc.rust-lang.org/std/convert/index.html) module. +The traits are the following: + +- `From` and `Into` covered in [`from_into`](from_into.rs) +- `TryFrom` and `TryInto` covered in [`try_from_into`](try_from_into.rs) +- `AsRef` and `AsMut` covered in [`as_ref_mut`](as_ref_mut.rs) + +Furthermore, the `std::str` module offers a trait called [`FromStr`](https://doc.rust-lang.org/std/str/trait.FromStr.html) which helps with converting strings into target types via the `parse` method on strings. If properly implemented for a given type `Person`, then `let p: Person = "Mark,20".parse().unwrap()` should both compile and run without panicking. + +These should be the main ways ***within the standard library*** to convert data into your desired types. + +## Further information + +These are not directly covered in the book, but the standard library has a great documentation for it. + +- [conversions](https://doc.rust-lang.org/std/convert/index.html) +- [`FromStr` trait](https://doc.rust-lang.org/std/str/trait.FromStr.html) diff --git a/exercises/23_conversions/as_ref_mut.rs b/exercises/23_conversions/as_ref_mut.rs new file mode 100644 index 0000000..2ba9e3f --- /dev/null +++ b/exercises/23_conversions/as_ref_mut.rs @@ -0,0 +1,65 @@ +// as_ref_mut.rs +// +// 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. +// +// Execute `rustlings hint as_ref_mut` or use the `hint` watch subcommand for a +// hint. + +// I AM NOT DONE + +// Obtain the number of bytes (not characters) in the given argument. +// TODO: Add the AsRef trait appropriately as a trait bound. +fn byte_counter<T>(arg: T) -> usize { + arg.as_ref().as_bytes().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<T>(arg: T) -> usize { + arg.as_ref().chars().count() +} + +// Squares a number using as_mut(). +// TODO: Add the appropriate trait bound. +fn num_sq<T>(arg: &mut T) { + // TODO: Implement the function body. + ??? +} + +#[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<u32> = Box::new(3); + num_sq(&mut num); + assert_eq!(*num, 9); + } +} diff --git a/exercises/23_conversions/from_into.rs b/exercises/23_conversions/from_into.rs new file mode 100644 index 0000000..60911f3 --- /dev/null +++ b/exercises/23_conversions/from_into.rs @@ -0,0 +1,140 @@ +// from_into.rs +// +// The From trait is used for value-to-value conversions. If From is implemented +// correctly for a type, the Into trait should work conversely. You can read +// more about it at https://doc.rust-lang.org/std/convert/trait.From.html +// +// Execute `rustlings hint from_into` or use the `hint` watch subcommand for a +// hint. + +#[derive(Debug)] +struct Person { + name: String, + age: usize, +} + +// 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() -> Person { + Person { + name: String::from("John"), + age: 30, + } + } +} + +// Your task is to complete this implementation in order for the line `let p = +// Person::from("Mark,20")` to compile Please note that you'll need to parse the +// age component into a `usize` with something like `"4".parse::<usize>()`. The +// outcome of this needs to be handled appropriately. +// +// Steps: +// 1. If the length of the provided string is 0, then return the default of +// Person. +// 2. Split the given string on the commas present in it. +// 3. Extract the first element from the split operation and use it as the name. +// 4. If the name is empty, then return the default of Person. +// 5. Extract the other element from the split operation and parse it into a +// `usize` as the age. +// If while parsing the age, something goes wrong, then return the default of +// Person Otherwise, then return an instantiated Person object with the results + +// I AM NOT DONE + +impl From<&str> for Person { + fn from(s: &str) -> Person { + } +} + +fn main() { + // Use the `from` function + let p1 = Person::from("Mark,20"); + // Since From is implemented for Person, we should be able to use Into + let p2: Person = "Gerald,70".into(); + println!("{:?}", p1); + println!("{:?}", p2); +} + +#[cfg(test)] +mod tests { + use super::*; + #[test] + fn test_default() { + // Test that the default person is 30 year old John + let dp = Person::default(); + assert_eq!(dp.name, "John"); + assert_eq!(dp.age, 30); + } + #[test] + fn test_bad_convert() { + // Test that John is returned when bad string is provided + let p = Person::from(""); + assert_eq!(p.name, "John"); + assert_eq!(p.age, 30); + } + #[test] + fn test_good_convert() { + // Test that "Mark,20" works + let p = Person::from("Mark,20"); + assert_eq!(p.name, "Mark"); + assert_eq!(p.age, 20); + } + #[test] + fn test_bad_age() { + // Test that "Mark,twenty" will return the default person due to an + // error in parsing 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, "Mike"); + assert_eq!(p.age, 32); + } + + #[test] + fn test_trailing_comma_and_some_string() { + let p: Person = Person::from("Mike,32,man"); + assert_eq!(p.name, "Mike"); + assert_eq!(p.age, 32); + } +} diff --git a/exercises/23_conversions/from_str.rs b/exercises/23_conversions/from_str.rs new file mode 100644 index 0000000..34472c3 --- /dev/null +++ b/exercises/23_conversions/from_str.rs @@ -0,0 +1,133 @@ +// from_str.rs +// +// This is similar to from_into.rs, 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 at +// https://doc.rust-lang.org/std/str/trait.FromStr.html +// +// Execute `rustlings hint from_str` or use the `hint` watch subcommand for a +// hint. + +use std::num::ParseIntError; +use std::str::FromStr; + +#[derive(Debug, PartialEq)] +struct Person { + name: String, + age: usize, +} + +// We will use this error type for the `FromStr` implementation. +#[derive(Debug, PartialEq)] +enum ParsePersonError { + // Empty input string + Empty, + // Incorrect number of fields + BadLen, + // Empty name field + NoName, + // Wrapped error from parse::<usize>() + ParseInt(ParseIntError), +} + +// I AM NOT DONE + +// Steps: +// 1. If the length of the provided string is 0, an error should be returned +// 2. Split the given string on the commas present in it +// 3. Only 2 elements should be returned from the split, otherwise return an +// error +// 4. Extract the first element from the split operation and use it as the name +// 5. Extract the other element from the split operation and parse it into a +// `usize` as the age with something like `"4".parse::<usize>()` +// 6. If while extracting the name and the age something goes wrong, an error +// should be returned +// If everything goes well, then return a Result of a Person object +// +// As an aside: `Box<dyn Error>` implements `From<&'_ str>`. This means that if +// you want to return a string error message, you can do so via just using +// return `Err("my error message".into())`. + +impl FromStr for Person { + type Err = ParsePersonError; + fn from_str(s: &str) -> Result<Person, Self::Err> { + } +} + +fn main() { + let p = "Mark,20".parse::<Person>().unwrap(); + println!("{:?}", p); +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn empty_input() { + assert_eq!("".parse::<Person>(), Err(ParsePersonError::Empty)); + } + #[test] + fn good_input() { + let p = "John,32".parse::<Person>(); + 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::<Person>(), + Err(ParsePersonError::ParseInt(_)) + )); + } + + #[test] + fn invalid_age() { + assert!(matches!( + "John,twenty".parse::<Person>(), + Err(ParsePersonError::ParseInt(_)) + )); + } + + #[test] + fn missing_comma_and_age() { + assert_eq!("John".parse::<Person>(), Err(ParsePersonError::BadLen)); + } + + #[test] + fn missing_name() { + assert_eq!(",1".parse::<Person>(), Err(ParsePersonError::NoName)); + } + + #[test] + fn missing_name_and_age() { + assert!(matches!( + ",".parse::<Person>(), + Err(ParsePersonError::NoName | ParsePersonError::ParseInt(_)) + )); + } + + #[test] + fn missing_name_and_invalid_age() { + assert!(matches!( + ",one".parse::<Person>(), + Err(ParsePersonError::NoName | ParsePersonError::ParseInt(_)) + )); + } + + #[test] + fn trailing_comma() { + assert_eq!("John,32,".parse::<Person>(), Err(ParsePersonError::BadLen)); + } + + #[test] + fn trailing_comma_and_some_string() { + assert_eq!( + "John,32,man".parse::<Person>(), + Err(ParsePersonError::BadLen) + ); + } +} diff --git a/exercises/23_conversions/try_from_into.rs b/exercises/23_conversions/try_from_into.rs new file mode 100644 index 0000000..32d6ef3 --- /dev/null +++ b/exercises/23_conversions/try_from_into.rs @@ -0,0 +1,193 @@ +// try_from_into.rs +// +// 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 at +// https://doc.rust-lang.org/std/convert/trait.TryFrom.html +// +// Execute `rustlings hint try_from_into` or use the `hint` watch subcommand for +// a hint. + +use std::convert::{TryFrom, TryInto}; + +#[derive(Debug, PartialEq)] +struct Color { + red: u8, + green: u8, + blue: u8, +} + +// We will use this error type for these `TryFrom` conversions. +#[derive(Debug, PartialEq)] +enum IntoColorError { + // Incorrect length of slice + BadLen, + // Integer conversion error + IntConversion, +} + +// I AM NOT DONE + +// Your task is to complete this implementation and return an Ok result of inner +// type Color. You need to create an implementation for a tuple of three +// integers, an array of three integers, and a slice of integers. +// +// Note that the implementation for tuple and array will be checked at compile +// time, but the slice implementation needs to check the slice length! Also note +// that correct RGB color values must be integers in the 0..=255 range. + +// Tuple implementation +impl TryFrom<(i16, i16, i16)> for Color { + type Error = IntoColorError; + fn try_from(tuple: (i16, i16, i16)) -> Result<Self, Self::Error> { + } +} + +// Array implementation +impl TryFrom<[i16; 3]> for Color { + type Error = IntoColorError; + fn try_from(arr: [i16; 3]) -> Result<Self, Self::Error> { + } +} + +// Slice implementation +impl TryFrom<&[i16]> for Color { + type Error = IntoColorError; + fn try_from(slice: &[i16]) -> Result<Self, Self::Error> { + } +} + +fn main() { + // Use the `try_from` function + let c1 = Color::try_from((183, 65, 14)); + println!("{:?}", c1); + + // Since TryFrom is implemented for Color, we should be able to use TryInto + let c2: Result<Color, _> = [183, 65, 14].try_into(); + println!("{:?}", c2); + + let v = vec![183, 65, 14]; + // With slice we should use `try_from` function + let c3 = Color::try_from(&v[..]); + println!("{:?}", c3); + // or take slice within round brackets and use TryInto + let c4: Result<Color, _> = (&v[..]).try_into(); + println!("{:?}", c4); +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_tuple_out_of_range_positive() { + assert_eq!( + Color::try_from((256, 1000, 10000)), + Err(IntoColorError::IntConversion) + ); + } + #[test] + fn test_tuple_out_of_range_negative() { + assert_eq!( + Color::try_from((-1, -10, -256)), + Err(IntoColorError::IntConversion) + ); + } + #[test] + fn test_tuple_sum() { + assert_eq!( + Color::try_from((-1, 255, 255)), + Err(IntoColorError::IntConversion) + ); + } + #[test] + fn test_tuple_correct() { + let c: Result<Color, _> = (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<Color, _> = [1000, 10000, 256].try_into(); + assert_eq!(c, Err(IntoColorError::IntConversion)); + } + #[test] + fn test_array_out_of_range_negative() { + let c: Result<Color, _> = [-10, -256, -1].try_into(); + assert_eq!(c, Err(IntoColorError::IntConversion)); + } + #[test] + fn test_array_sum() { + let c: Result<Color, _> = [-1, 255, 255].try_into(); + assert_eq!(c, Err(IntoColorError::IntConversion)); + } + #[test] + fn test_array_correct() { + let c: Result<Color, _> = [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(IntoColorError::IntConversion) + ); + } + #[test] + fn test_slice_out_of_range_negative() { + let arr = [-256, -1, -10]; + assert_eq!( + Color::try_from(&arr[..]), + Err(IntoColorError::IntConversion) + ); + } + #[test] + fn test_slice_sum() { + let arr = [-1, 255, 255]; + assert_eq!( + Color::try_from(&arr[..]), + Err(IntoColorError::IntConversion) + ); + } + #[test] + fn test_slice_correct() { + let v = vec![183, 65, 14]; + let c: Result<Color, _> = 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(IntoColorError::BadLen)); + } + #[test] + fn test_slice_insufficient_length() { + let v = vec![0, 0]; + assert_eq!(Color::try_from(&v[..]), Err(IntoColorError::BadLen)); + } +} diff --git a/exercises/23_conversions/using_as.rs b/exercises/23_conversions/using_as.rs new file mode 100644 index 0000000..414cef3 --- /dev/null +++ b/exercises/23_conversions/using_as.rs @@ -0,0 +1,33 @@ +// using_as.rs +// +// Type casting in Rust is done via the usage of the `as` operator. Please note +// that the `as` operator is not only used when type casting. It also helps with +// renaming imports. +// +// The goal is to make sure that the division does not fail to compile and +// returns the proper type. +// +// Execute `rustlings hint using_as` or use the `hint` watch subcommand for a +// hint. + +// I AM NOT DONE + +fn average(values: &[f64]) -> f64 { + let total = values.iter().sum::<f64>(); + total / values.len() +} + +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); + } +} |
