Type conversions
Rust offers a multitude of ways to convert a value of a given type into another type.
- as: 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 tries to cover this.
- from/into/AsRef: Rust also offers traits that
facilitate type conversionsupon implementation.
These traits can be found under the
convertmodule.
The traits are the following:
FromandIntocovered infrom_intoTryFromandTryIntocovered intry_from_intoAsRefandAsMutcovered inas_ref_mut
- from_str: Furthermore, the
std::strmodule offers a trait calledFromStrwhich helps with converting strings into target types via theparsemethod on strings.
If properly implemented for a given type
Person, thenlet 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.
Rustlings
using as: type casting
- Type casting in Rust is done via the usage of the
asoperator. - Please note that the
asoperator 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.
using_as
// 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)); // convert unit tests to here to run in the rust playground fn returns_proper_type_and_value() { assert_eq!(average(&[3.5, 0.3, 13.0, 11.7]), 7.125); } returns_proper_type_and_value(); } #[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); } }
Hint
Use the as operator to cast one of the operands in the last line of the
average function into the expected return type.
solution
fn average(values: &[f64]) -> f64 { let total = values.iter().sum::<f64>(); total / values.len() as f64 }
from_into: value-to-value
- The From trait is used for
value-to-valueconversions.
If From is implemented correctly for a type, the Into trait should work conversely.
- You can read more about it at: From in std::convert - Rust
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
usizewith something like"4".parse::<usize>(). - The outcome of this needs to be handled appropriately.
Steps:
- If the length of the provided string is 0, then return the default of Person
- Split the given string on the commas present in it
- Extract the first element from the split operation and use it as the name
- If the name is empty, then return the default of Person
- Extract the other element from the split operation and parse it into a
usizeas 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
🌟from_into: We implement the Default trait to use it as a fallback when the provided string is not convertible into a Person object
// 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); // convert unit tests to here to run in the rust playground // 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 that John is returned when bad string is provided let p = Person::from(""); assert_eq!(p.name, "John"); assert_eq!(p.age, 30); // Test that "Mark,20" works let p = Person::from("Mark,20"); assert_eq!(p.name, "Mark"); assert_eq!(p.age, 20); // 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); let p: Person = Person::from("Mark"); assert_eq!(p.name, "John"); assert_eq!(p.age, 30); let p: Person = Person::from("Mark,"); assert_eq!(p.name, "John"); assert_eq!(p.age, 30); let p: Person = Person::from(",1"); assert_eq!(p.name, "John"); assert_eq!(p.age, 30); let p: Person = Person::from(","); assert_eq!(p.name, "John"); assert_eq!(p.age, 30); let p: Person = Person::from(",one"); assert_eq!(p.name, "John"); assert_eq!(p.age, 30); let p: Person = Person::from("Mike,32,"); assert_eq!(p.name, "John"); assert_eq!(p.age, 30); let p: Person = Person::from("Mike,32,man"); assert_eq!(p.name, "John"); assert_eq!(p.age, 30); } #[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, "John"); assert_eq!(p.age, 30); } #[test] fn test_trailing_comma_and_some_string() { let p: Person = Person::from("Mike,32,man"); assert_eq!(p.name, "John"); assert_eq!(p.age, 30); } }
solution: match -> split_once -> parse:: -> into() or default()
impl From<&str> for Person { fn from(s: &str) -> Person { // 2. Split the given string on the commas present in it match s.split_once(',') { Some((first, second)) => { if first.is_empty() { // 4. If the name is empty, then return the default of Person Person::default() } else if let Ok(a) = second.parse::<usize>() { // 5. Extract the other element from the split operation and parse it into a `usize` as the age Person { name: first.into(), // 3. Extract the first element from the split operation and use it as the name age: a, // 6. If while parsing the age, something goes wrong, then return the default of Person } } else { Person::default() // 7. Otherwise, then return an instantiated Person object with the results } }, _ => Person::default(), // 1. If the length of the provided string is 0, then return the default of Person } } }
try_from_into: try value-to-value
- TryFrom is a simple and safe type conversion that may fail in
a controlled wayunder 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: TryFrom in std::convert - Rust
Your task is to complete this implementation:
- return an Ok result of inner type Color.
- You need to create an implementation for a
tupleof three integers, anarrayof three integers, and asliceof 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.
⚡️try_from_into: with challenge
// 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); // convert unit tests to here to run in the rust playground fn test_tuple_out_of_range_positive() { assert_eq!( Color::try_from((256, 1000, 10000)), Err(IntoColorError::IntConversion) ); } fn test_tuple_out_of_range_negative() { assert_eq!( Color::try_from((-1, -10, -256)), Err(IntoColorError::IntConversion) ); } fn test_tuple_sum() { assert_eq!( Color::try_from((-1, 255, 255)), Err(IntoColorError::IntConversion) ); } 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 } ); } fn test_array_out_of_range_positive() { let c: Result<Color, _> = [1000, 10000, 256].try_into(); assert_eq!(c, Err(IntoColorError::IntConversion)); } fn test_array_out_of_range_negative() { let c: Result<Color, _> = [-10, -256, -1].try_into(); assert_eq!(c, Err(IntoColorError::IntConversion)); } fn test_array_sum() { let c: Result<Color, _> = [-1, 255, 255].try_into(); assert_eq!(c, Err(IntoColorError::IntConversion)); } 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 } ); } fn test_slice_out_of_range_positive() { let arr = [10000, 256, 1000]; assert_eq!( Color::try_from(&arr[..]), Err(IntoColorError::IntConversion) ); } fn test_slice_out_of_range_negative() { let arr = [-256, -1, -10]; assert_eq!( Color::try_from(&arr[..]), Err(IntoColorError::IntConversion) ); } fn test_slice_sum() { let arr = [-1, 255, 255]; assert_eq!( Color::try_from(&arr[..]), Err(IntoColorError::IntConversion) ); } 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 } ); } fn test_slice_excess_length() { let v = vec![0, 0, 0, 0]; assert_eq!(Color::try_from(&v[..]), Err(IntoColorError::BadLen)); } fn test_slice_insufficient_length() { let v = vec![0, 0]; assert_eq!(Color::try_from(&v[..]), Err(IntoColorError::BadLen)); } test_tuple_out_of_range_positive(); test_tuple_out_of_range_negative(); test_tuple_sum(); test_tuple_correct(); test_array_out_of_range_positive(); test_array_out_of_range_negative(); test_array_sum(); test_array_correct(); test_slice_out_of_range_positive(); test_slice_out_of_range_negative(); test_slice_sum(); test_slice_correct(); test_slice_excess_length(); test_slice_insufficient_length(); } #[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)); } }
Hint
- Follow the steps provided right before the
TryFromimplementation. You can also use the example at: TryFrom in std::convert - Rust
Is there an implementation of TryFrom in the standard library that
can both do the required integer conversion and check the range of the input?
-
Another hint: Look at the test cases to see which error variants to return.
-
Yet another hint: You can use the
map_errorormethods ofResultto convert errors. -
Yet another hint: If you would like to propagate errors by using the
?operator in your solution, you might want to look at: Other uses of ? - The Rust Programming Language
solution: essentially return to run the implementation of tuple
// Tuple implementation impl TryFrom<(i16, i16, i16)> for Color { type Error = IntoColorError; fn try_from(tuple: (i16, i16, i16)) -> Result<Self, Self::Error> { for i in [tuple.0, tuple.1, tuple.2] { if i < 0 || i > 255 { return Err(IntoColorError::IntConversion) } } // return an Ok result of inner type Color. Ok(Color { red: tuple.0 as u8, green: tuple.1 as u8, blue: tuple.2 as u8 }) } } // Array implementation impl TryFrom<[i16; 3]> for Color { type Error = IntoColorError; fn try_from(arr: [i16; 3]) -> Result<Self, Self::Error> { // return to run the implementation of tuple above Self::try_from((arr[0], arr[1], arr[2])) } } // Slice implementation impl TryFrom<&[i16]> for Color { type Error = IntoColorError; fn try_from(slice: &[i16]) -> Result<Self, Self::Error> { if slice.len() != 3 { Err(IntoColorError::BadLen) } else { // return to run the implementation of tuple above Self::try_from((slice[0], slice[1], slice[2])) } } }
as_ref_mut: reference-to-reference
AsRefandAsMutallow for cheap reference-to-reference conversions.
Read more about them at AsRef in std::convert - Rust and AsMut in std::convert - Rust, respectively.
as_ref_mut: Three ways to add appropriate trait bound
// 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 mult_box() { let mut num: Box<u32> = Box::new(3); num_sq(&mut num); assert_eq!(*num, 9); } } // convert the unit test to main fn main() { // different_counts() let s = "Café au lait"; assert_ne!(char_counter(s), byte_counter(s)); // same_counts let s = "Cafe au lait"; assert_eq!(char_counter(s), byte_counter(s)); // different_counts_using_string let s = String::from("Café au lait"); assert_ne!(char_counter(s.clone()), byte_counter(s)); // same_counts_using_string let s = String::from("Cafe au lait"); assert_eq!(char_counter(s.clone()), byte_counter(s)); // mult_box let mut num: Box<u32> = Box::new(3); num_sq(&mut num); assert_eq!(*num, 9); }
compare to traits4
solution1: generic trait bounds
// Obtain the number of bytes (not characters) in the given argument // Add the AsRef trait appropriately as a trait bound fn byte_counter<T: AsRef<str>>(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<T: AsRef<str>>(arg: T) -> usize { arg.as_ref().chars().count() } // Squares a number using AsMut. Add the trait bound as is appropriate and // implement the function body. fn num_sq<T: AsMut<u32>>(arg: &mut T) { *arg.as_mut() *= *arg.as_mut() }
solution2: impl trait bounds
// Obtain the number of bytes (not characters) in the given argument // Add the AsRef trait appropriately as a trait bound fn byte_counter(arg: impl AsRef<str>) -> 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_counterd(arg: impl AsRef<str>) -> usize { arg.as_ref().chars().count() } // Squares a number using AsMut. Add the trait bound as is appropriate and // implement the function body. fn num_sq(arg: &mut impl AsRef<str>) { *arg.as_mut() *= *arg.as_mut() }
solution3: where clause
// Obtain the number of bytes (not characters) in the given argument // Add the AsRef trait appropriately as a trait bound fn byte_counter<T>(arg: T) -> usize where T: AsRef<str> { arg.as_ref().as_bytes().len() }
from_str: convert str to target type
- This is similar to from_into.rs, but this time we’ll implement
FromStrand return errors instead of falling back to a default value. - Additionally, upon implementing FromStr, you can use the
parsemethod on strings to generate an object of the implementor type. - You can read more about it at FromStr in std::str - Rust
Steps:
- If the length of the provided string is 0, an error should be returned
- Split the given string on the commas present in it
- Only 2 elements should be returned from the split, otherwise return an error
- Extract the first element from the split operation and use it as the name
- Extract the other element from the split operation and parse it into a
usizeas the age with something like"4".parse::<usize>() - 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()).
from_str
// 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); // convert unit tests to here to run in the rust playground assert_eq!("".parse::<Person>(), Err(ParsePersonError::Empty)); let p = "John,32".parse::<Person>(); assert!(p.is_ok()); let p = p.unwrap(); assert_eq!(p.name, "John"); assert_eq!(p.age, 32); assert!(matches!( "John,".parse::<Person>(), Err(ParsePersonError::ParseInt(_)) )); assert!(matches!( "John,twenty".parse::<Person>(), Err(ParsePersonError::ParseInt(_)) )); assert_eq!("John".parse::<Person>(), Err(ParsePersonError::BadLen)); assert_eq!(",1".parse::<Person>(), Err(ParsePersonError::NoName)); assert!(matches!( ",".parse::<Person>(), Err(ParsePersonError::NoName | ParsePersonError::ParseInt(_)) )); assert!(matches!( ",one".parse::<Person>(), Err(ParsePersonError::NoName | ParsePersonError::ParseInt(_)) )); assert_eq!("John,32,".parse::<Person>(), Err(ParsePersonError::BadLen)); assert_eq!( "John,32,man".parse::<Person>(), Err(ParsePersonError::BadLen) ); } #[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) ); } }
Hint
- The implementation of FromStr should return an Ok with a Person object, or an Err with an error if the string is not valid.
This is almost like the from_into exercise, but returning errors instead
of falling back to a default value.
Look at the test cases to see which error variants to return.
-
Another hint: You can use the
map_errmethod ofResultwith a function or a closure to wrap the error fromparse::<usize>. -
Yet another hint: If you would like to propagate errors by using the
?operator in your solution, you might want to look at Other uses of ? - The Rust Programming Language
solution1
// Using ParsePersonError::{Empty, BadLen, NoName, ParseInt} above impl FromStr for Person { type Err = ParsePersonError; fn from_str(s: &str) -> Result<Person, Self::Err> { if s.is_empty() { Err(Empty) // i.e., Err(ParsePersonError::Empty) } else { let p: Vec<&str> = s.split(',').collect(); if p.len() != 2 { Err(BadLen) } else if p[0].len() == 0 { Err(NoName) } else { match p[1].parse::<usize>() { Ok(a) => Ok(Person { name: p[0].to_string(), age: a }), Err(a) => Err(ParseInt(a)), } } } } }
solution2: Err::Empty -> Err is a variant, not a module
impl FromStr for Person { type Err = ParsePersonError; fn from_str(s: &str) -> Result<Person, Self::Err> { if s.is_empty() { Err::Empty // i.e., Err(ParsePersonError::Empty) } else { let p: Vec<&str> = s.split(',').collect(); if p.len() != 2 { Err::BadLen } else if p[0].len() == 0 { Err::NoName } else { match p[1].parse::<usize>() { Ok(a) => Ok(Person { name: p[0].to_string(), age: a }), Err(a) => Err::ParseInt(a), } } } } }
solution3: Err.Empty()
impl FromStr for Person { type Err = ParsePersonError; fn from_str(s: &str) -> Result<Person, Self::Err> { if s.is_empty() { Err.Empty // i.e., Err(ParsePersonError::Empty) } else { let p: Vec<&str> = s.split(',').collect(); if p.len() != 2 { Err.BadLen } else if p[0].len() == 0 { Err.NoName } else { match p[1].parse::<usize>() { Ok(a) => Ok(Person { name: p[0].to_string(), age: a }), Err(a) => Err.ParseInt(a), } } } } }
solution4: ParsePersonError::{ErrorType}
// Using ParsePersonError::{Empty, BadLen, NoName, ParseInt} above impl FromStr for Person { type Err = ParsePersonError; fn from_str(s: &str) -> Result<Person, Self::Err> { if s.is_empty() { // 1. If the length of the provided string is 0, an error should be returned Err(ParsePersonError::Empty) // i.e., Err(ParsePersonError::Empty) } else { let p: Vec<&str> = s.split(',').collect(); // 2. Split the given string on the commas present in it if p.len() != 2 { // 3. Only 2 elements should be returned from the split, otherwise return an error Err(ParsePersonError::BadLen) } else if p[0].len() == 0 { // 4. Extract the first element from the split operation and use it as the name Err(ParsePersonError::NoName) } else { match p[1].parse::<usize>() { // 5. Extract the other element from the split operation and parse it into a `usize` as the age with something like `"4".parse::<usize>()` Ok(a) => Ok(Person { name: p[0].to_string(), age: a }), // 7. If everything goes well, then return a Result of a Person object Err(a) => Err(ParsePersonError::ParseInt(a)), // 6. If while extracting the name and the age something goes wrong, an error should be returned } } } } }