diff --git a/exercises/conversions/README.md b/exercises/conversions/README.md new file mode 100644 index 0000000000..114bd4281a --- /dev/null +++ b/exercises/conversions/README.md @@ -0,0 +1,20 @@ +### 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. + +#### Book Sections + +These are not directly covered in the book, but the standard library has great documentation for [conversions here](https://doc.rust-lang.org/std/convert/index.html). The `FromStr` trait is also covered [here](https://doc.rust-lang.org/std/str/trait.FromStr.html). \ No newline at end of file diff --git a/exercises/conversions/as_ref_mut.rs b/exercises/conversions/as_ref_mut.rs new file mode 100644 index 0000000000..9d92fff63b --- /dev/null +++ b/exercises/conversions/as_ref_mut.rs @@ -0,0 +1,36 @@ +// 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 +// Add the AsRef trait appropriately as a trait bound +fn byte_counter(arg: T) -> usize { + arg.as_ref().as_bytes().len() +} + +// Obtain the number of characters (not bytes) in the given argument +// Add the AsRef trait appropriately as a trait bound +fn char_counter(arg: T) -> usize { + arg.as_ref().chars().collect::>().len() +} + +fn main() { + let s = "Café au lait"; + println!("{}", char_counter(s)); + println!("{}", byte_counter(s)); +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn different_counts() { + let s = "Café au lait"; + assert_ne!(char_counter(s), byte_counter(s)); + } + fn same_counts() { + let s = "Cafe au lait"; + assert_eq!(char_counter(s), byte_counter(s)); + } +} \ No newline at end of file diff --git a/exercises/conversions/from_into.rs b/exercises/conversions/from_into.rs new file mode 100644 index 0000000000..c988ee8d73 --- /dev/null +++ b/exercises/conversions/from_into.rs @@ -0,0 +1,72 @@ +// 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 +#[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::()`. 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. 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 onject with the results +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); + } +} diff --git a/exercises/conversions/from_str.rs b/exercises/conversions/from_str.rs new file mode 100644 index 0000000000..247ae14309 --- /dev/null +++ b/exercises/conversions/from_str.rs @@ -0,0 +1,48 @@ +// This does practically the same thing that TryFrom<&str> does. +// 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 +use std::str::FromStr; + +#[derive(Debug)] +struct Person { + name: String, + age: usize, +} + +// Steps: +// 1. If the length of the provided string is 0, then return an error +// 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. 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 an error +// Otherwise, then return a Result of a Person object +impl FromStr for Person { + type Err = String; + fn from_str(s: &str) -> Result { + } +} + +fn main() { + let p = "Mark,20".parse::().unwrap(); + println!("{:?}", p); +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn empty_input() { + assert!("".parse::().is_err()); + } + #[test] + fn good_input() { + assert!("John,32".parse::().is_ok()); + } + #[test] + #[should_panic] + fn missing_age() { + "John".parse::().unwrap(); + } +} \ No newline at end of file diff --git a/exercises/conversions/try_from_into.rs b/exercises/conversions/try_from_into.rs new file mode 100644 index 0000000000..ff77089a92 --- /dev/null +++ b/exercises/conversions/try_from_into.rs @@ -0,0 +1,70 @@ +// 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 +use std::convert::{TryInto, TryFrom}; + +#[derive(Debug)] +struct Person { + name: String, + age: usize, +} + +// Your task is to complete this implementation +// in order for the line `let p = Person::try_from("Mark,20")` to compile +// and return an Ok result of inner type Person. +// Please note that you'll need to parse the age component into a `usize` +// with something like `"4".parse::()`. The outcome of this needs to +// be handled appropriately. +// +// Steps: +// 1. If the length of the provided string is 0, then return an error +// 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. 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 an error +// Otherwise, then return a Result of a Person object +impl TryFrom<&str> for Person { + type Error = String; + fn try_from(s: &str) -> Result { + } +} + +fn main() { + // Use the `from` function + let p1 = Person::try_from("Mark,20"); + // Since From is implemented for Person, we should be able to use Into + let p2: Result = "Gerald,70".try_into(); + println!("{:?}", p1); + println!("{:?}", p2); +} + +#[cfg(test)] +mod tests { + use super::*; + #[test] + fn test_bad_convert() { + // Test that John is returned when bad string is provided + let p = Person::try_from(""); + assert!(p.is_err()); + } + #[test] + fn test_good_convert() { + // Test that "Mark,20" works + let p = Person::try_from("Mark,20"); + assert!(p.is_ok()); + let p = p.unwrap(); + assert_eq!(p.name, "Mark"); + assert_eq!(p.age, 20); + } + #[test] + #[should_panic] + fn test_panic_empty_input() { + let p: Person = "".try_into().unwrap(); + } + #[test] + #[should_panic] + fn test_panic_bad_age() { + let p = Person::try_from("Mark,twenty").unwrap(); + } +} \ No newline at end of file diff --git a/exercises/conversions/using_as.rs b/exercises/conversions/using_as.rs new file mode 100644 index 0000000000..37eb69f244 --- /dev/null +++ b/exercises/conversions/using_as.rs @@ -0,0 +1,16 @@ +// 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 +fn average(values: &[f64]) -> f64 { + let total = values + .iter() + .fold(0.0, |a, b| a + b); + total / values.len() +} + +fn main() { + let values = [3.5, 0.3, 13.0, 11.7]; + println!("{}", average(&values)); +} \ No newline at end of file diff --git a/info.toml b/info.toml index 99628d189f..3ca1824b72 100644 --- a/info.toml +++ b/info.toml @@ -610,3 +610,43 @@ answers and don't understand why they work and yours doesn't. If you've learned from the sample solutions, I encourage you to come back to this exercise and try it again in a few days to reinforce what you've learned :)""" + +# TYPE CONVERSIONS + +[[exercises]] +name = "using_as" +path = "exercises/conversions/using_as.rs" +mode = "compile" +hint = """ +Use the `as` operator to cast one of the operands in the last line of the +`average` function into the expected return type.""" + +[[exercises]] +name = "from_into" +path = "exercises/conversions/from_into.rs" +mode = "test" +hint = """ +Follow the steps provided right before the `From` implementation""" + +[[exercises]] +name = "try_from_into" +path = "exercises/conversions/try_from_into.rs" +mode = "test" +hint = """ +Follow the steps provided right before the `From` implementation. +You can also use the example at https://doc.rust-lang.org/std/convert/trait.TryFrom.html""" + +[[exercises]] +name = "as_ref_mut" +path = "exercises/conversions/as_ref_mut.rs" +mode = "test" +hint = """ +Add AsRef as a trait bound to the functions.""" + +[[exercises]] +name = "from_str" +path = "exercises/conversions/from_str.rs" +mode = "test" +hint = """ +If you've already solved try_from_into.rs, then this is almost a copy-paste. +Otherwise, go ahead and solve try_from_into.rs first.""" \ No newline at end of file