Auto merge of #249 - AbdouSeck:conversions, r=fmoko
feat: Add type conversion and parsing exercises This pull request adds exercises for converting values into specific types. The exercises uses string to struct type conversions, but most of the traits in the exercises can handle more than just string parsing and conversions. The following traits are covered: 1. `From` and `Into` 2. `TryFrom` and `TryInto` 3. `AsRef` 4. `FromStr` The `as` operator is also covered.
This commit is contained in:
commit
426a7bb87f
7 changed files with 308 additions and 0 deletions
20
exercises/conversions/README.md
Normal file
20
exercises/conversions/README.md
Normal file
|
@ -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).
|
38
exercises/conversions/as_ref_mut.rs
Normal file
38
exercises/conversions/as_ref_mut.rs
Normal file
|
@ -0,0 +1,38 @@
|
|||
// 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.
|
||||
|
||||
// I AM NOT DONE
|
||||
// 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 {
|
||||
arg.as_ref().as_bytes().len()
|
||||
}
|
||||
|
||||
// I AM NOT DONE
|
||||
// Obtain the number of characters (not bytes) in the given argument
|
||||
// Add the AsRef trait appropriately as a trait bound
|
||||
fn char_counter<T>(arg: T) -> usize {
|
||||
arg.as_ref().chars().collect::<Vec<_>>().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));
|
||||
}
|
||||
}
|
73
exercises/conversions/from_into.rs
Normal file
73
exercises/conversions/from_into.rs
Normal file
|
@ -0,0 +1,73 @@
|
|||
// 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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// I AM NOT DONE
|
||||
// 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. 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);
|
||||
}
|
||||
}
|
49
exercises/conversions/from_str.rs
Normal file
49
exercises/conversions/from_str.rs
Normal file
|
@ -0,0 +1,49 @@
|
|||
// 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,
|
||||
}
|
||||
|
||||
// I AM NOT DONE
|
||||
// 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<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!("".parse::<Person>().is_err());
|
||||
}
|
||||
#[test]
|
||||
fn good_input() {
|
||||
assert!("John,32".parse::<Person>().is_ok());
|
||||
}
|
||||
#[test]
|
||||
#[should_panic]
|
||||
fn missing_age() {
|
||||
"John".parse::<Person>().unwrap();
|
||||
}
|
||||
}
|
71
exercises/conversions/try_from_into.rs
Normal file
71
exercises/conversions/try_from_into.rs
Normal file
|
@ -0,0 +1,71 @@
|
|||
// 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,
|
||||
}
|
||||
|
||||
// I AM NOT DONE
|
||||
// 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::<usize>()`. 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<Self, Self::Error> {
|
||||
}
|
||||
}
|
||||
|
||||
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<Person, _> = "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();
|
||||
}
|
||||
}
|
17
exercises/conversions/using_as.rs
Normal file
17
exercises/conversions/using_as.rs
Normal file
|
@ -0,0 +1,17 @@
|
|||
// 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.
|
||||
|
||||
// I AM NOT DONE
|
||||
// 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));
|
||||
}
|
40
info.toml
40
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<str> 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."""
|
Loading…
Reference in a new issue