Skip to content

Commit 426a7bb

Browse files
committed
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.
2 parents fe10e06 + fc26b5e commit 426a7bb

File tree

7 files changed

+308
-0
lines changed

7 files changed

+308
-0
lines changed

exercises/conversions/README.md

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
### Type conversions
2+
3+
4+
Rust offers a multitude of ways to convert a value of a given type into another type.
5+
6+
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.
7+
8+
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.
9+
The traits are the following:
10+
- `From` and `Into` covered in [`from_into`](from_into.rs)
11+
- `TryFrom` and `TryInto` covered in [`try_from_into`](try_from_into.rs)
12+
- `AsRef` and `AsMut` covered in [`as_ref_mut`](as_ref_mut.rs)
13+
14+
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.
15+
16+
These should be the main ways ***within the standard library*** to convert data into your desired types.
17+
18+
#### Book Sections
19+
20+
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).

exercises/conversions/as_ref_mut.rs

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
// AsRef and AsMut allow for cheap reference-to-reference conversions.
2+
// Read more about them at https://doc.rust-lang.org/std/convert/trait.AsRef.html
3+
// and https://doc.rust-lang.org/std/convert/trait.AsMut.html, respectively.
4+
5+
// I AM NOT DONE
6+
// Obtain the number of bytes (not characters) in the given argument
7+
// Add the AsRef trait appropriately as a trait bound
8+
fn byte_counter<T>(arg: T) -> usize {
9+
arg.as_ref().as_bytes().len()
10+
}
11+
12+
// I AM NOT DONE
13+
// Obtain the number of characters (not bytes) in the given argument
14+
// Add the AsRef trait appropriately as a trait bound
15+
fn char_counter<T>(arg: T) -> usize {
16+
arg.as_ref().chars().collect::<Vec<_>>().len()
17+
}
18+
19+
fn main() {
20+
let s = "Café au lait";
21+
println!("{}", char_counter(s));
22+
println!("{}", byte_counter(s));
23+
}
24+
25+
#[cfg(test)]
26+
mod tests {
27+
use super::*;
28+
29+
#[test]
30+
fn different_counts() {
31+
let s = "Café au lait";
32+
assert_ne!(char_counter(s), byte_counter(s));
33+
}
34+
fn same_counts() {
35+
let s = "Cafe au lait";
36+
assert_eq!(char_counter(s), byte_counter(s));
37+
}
38+
}

exercises/conversions/from_into.rs

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
// The From trait is used for value-to-value conversions.
2+
// If From is implemented correctly for a type, the Into trait should work conversely.
3+
// You can read more about it at https://doc.rust-lang.org/std/convert/trait.From.html
4+
#[derive(Debug)]
5+
struct Person {
6+
name: String,
7+
age: usize,
8+
}
9+
10+
// We implement the Default trait to use it as a fallback
11+
// when the provided string is not convertible into a Person object
12+
impl Default for Person {
13+
fn default() -> Person {
14+
Person {
15+
name: String::from("John"),
16+
age: 30,
17+
}
18+
}
19+
}
20+
21+
// I AM NOT DONE
22+
// Your task is to complete this implementation
23+
// in order for the line `let p = Person::from("Mark,20")` to compile
24+
// Please note that you'll need to parse the age component into a `usize`
25+
// with something like `"4".parse::<usize>()`. The outcome of this needs to
26+
// be handled appropriately.
27+
//
28+
// Steps:
29+
// 1. If the length of the provided string is 0, then return the default of Person
30+
// 2. Split the given string on the commas present in it
31+
// 3. Extract the first element from the split operation and use it as the name
32+
// 4. Extract the other element from the split operation and parse it into a `usize` as the age
33+
// If while parsing the age, something goes wrong, then return the default of Person
34+
// Otherwise, then return an instantiated Person onject with the results
35+
impl From<&str> for Person {
36+
fn from(s: &str) -> Person {
37+
}
38+
}
39+
40+
fn main() {
41+
// Use the `from` function
42+
let p1 = Person::from("Mark,20");
43+
// Since From is implemented for Person, we should be able to use Into
44+
let p2: Person = "Gerald,70".into();
45+
println!("{:?}", p1);
46+
println!("{:?}", p2);
47+
}
48+
49+
#[cfg(test)]
50+
mod tests {
51+
use super::*;
52+
#[test]
53+
fn test_default() {
54+
// Test that the default person is 30 year old John
55+
let dp = Person::default();
56+
assert_eq!(dp.name, "John");
57+
assert_eq!(dp.age, 30);
58+
}
59+
#[test]
60+
fn test_bad_convert() {
61+
// Test that John is returned when bad string is provided
62+
let p = Person::from("");
63+
assert_eq!(p.name, "John");
64+
assert_eq!(p.age, 30);
65+
}
66+
#[test]
67+
fn test_good_convert() {
68+
// Test that "Mark,20" works
69+
let p = Person::from("Mark,20");
70+
assert_eq!(p.name, "Mark");
71+
assert_eq!(p.age, 20);
72+
}
73+
}

exercises/conversions/from_str.rs

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
// This does practically the same thing that TryFrom<&str> does.
2+
// Additionally, upon implementing FromStr, you can use the `parse` method
3+
// on strings to generate an object of the implementor type.
4+
// You can read more about it at https://doc.rust-lang.org/std/str/trait.FromStr.html
5+
use std::str::FromStr;
6+
7+
#[derive(Debug)]
8+
struct Person {
9+
name: String,
10+
age: usize,
11+
}
12+
13+
// I AM NOT DONE
14+
// Steps:
15+
// 1. If the length of the provided string is 0, then return an error
16+
// 2. Split the given string on the commas present in it
17+
// 3. Extract the first element from the split operation and use it as the name
18+
// 4. Extract the other element from the split operation and parse it into a `usize` as the age
19+
// If while parsing the age, something goes wrong, then return an error
20+
// Otherwise, then return a Result of a Person object
21+
impl FromStr for Person {
22+
type Err = String;
23+
fn from_str(s: &str) -> Result<Person, Self::Err> {
24+
}
25+
}
26+
27+
fn main() {
28+
let p = "Mark,20".parse::<Person>().unwrap();
29+
println!("{:?}", p);
30+
}
31+
32+
#[cfg(test)]
33+
mod tests {
34+
use super::*;
35+
36+
#[test]
37+
fn empty_input() {
38+
assert!("".parse::<Person>().is_err());
39+
}
40+
#[test]
41+
fn good_input() {
42+
assert!("John,32".parse::<Person>().is_ok());
43+
}
44+
#[test]
45+
#[should_panic]
46+
fn missing_age() {
47+
"John".parse::<Person>().unwrap();
48+
}
49+
}
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
// TryFrom is a simple and safe type conversion that may fail in a controlled way under some circumstances.
2+
// Basically, this is the same as From. The main difference is that this should return a Result type
3+
// instead of the target type itself.
4+
// You can read more about it at https://doc.rust-lang.org/std/convert/trait.TryFrom.html
5+
use std::convert::{TryInto, TryFrom};
6+
7+
#[derive(Debug)]
8+
struct Person {
9+
name: String,
10+
age: usize,
11+
}
12+
13+
// I AM NOT DONE
14+
// Your task is to complete this implementation
15+
// in order for the line `let p = Person::try_from("Mark,20")` to compile
16+
// and return an Ok result of inner type Person.
17+
// Please note that you'll need to parse the age component into a `usize`
18+
// with something like `"4".parse::<usize>()`. The outcome of this needs to
19+
// be handled appropriately.
20+
//
21+
// Steps:
22+
// 1. If the length of the provided string is 0, then return an error
23+
// 2. Split the given string on the commas present in it
24+
// 3. Extract the first element from the split operation and use it as the name
25+
// 4. Extract the other element from the split operation and parse it into a `usize` as the age
26+
// If while parsing the age, something goes wrong, then return an error
27+
// Otherwise, then return a Result of a Person object
28+
impl TryFrom<&str> for Person {
29+
type Error = String;
30+
fn try_from(s: &str) -> Result<Self, Self::Error> {
31+
}
32+
}
33+
34+
fn main() {
35+
// Use the `from` function
36+
let p1 = Person::try_from("Mark,20");
37+
// Since From is implemented for Person, we should be able to use Into
38+
let p2: Result<Person, _> = "Gerald,70".try_into();
39+
println!("{:?}", p1);
40+
println!("{:?}", p2);
41+
}
42+
43+
#[cfg(test)]
44+
mod tests {
45+
use super::*;
46+
#[test]
47+
fn test_bad_convert() {
48+
// Test that John is returned when bad string is provided
49+
let p = Person::try_from("");
50+
assert!(p.is_err());
51+
}
52+
#[test]
53+
fn test_good_convert() {
54+
// Test that "Mark,20" works
55+
let p = Person::try_from("Mark,20");
56+
assert!(p.is_ok());
57+
let p = p.unwrap();
58+
assert_eq!(p.name, "Mark");
59+
assert_eq!(p.age, 20);
60+
}
61+
#[test]
62+
#[should_panic]
63+
fn test_panic_empty_input() {
64+
let p: Person = "".try_into().unwrap();
65+
}
66+
#[test]
67+
#[should_panic]
68+
fn test_panic_bad_age() {
69+
let p = Person::try_from("Mark,twenty").unwrap();
70+
}
71+
}

exercises/conversions/using_as.rs

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
// Type casting in Rust is done via the usage of the `as` operator.
2+
// Please note that the `as` operator is not only used when type casting.
3+
// It also helps with renaming imports.
4+
5+
// I AM NOT DONE
6+
// The goal is to make sure that the division does not fail to compile
7+
fn average(values: &[f64]) -> f64 {
8+
let total = values
9+
.iter()
10+
.fold(0.0, |a, b| a + b);
11+
total / values.len()
12+
}
13+
14+
fn main() {
15+
let values = [3.5, 0.3, 13.0, 11.7];
16+
println!("{}", average(&values));
17+
}

info.toml

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -610,3 +610,43 @@ answers and don't understand why they work and yours doesn't.
610610
If you've learned from the sample solutions, I encourage you to come
611611
back to this exercise and try it again in a few days to reinforce
612612
what you've learned :)"""
613+
614+
# TYPE CONVERSIONS
615+
616+
[[exercises]]
617+
name = "using_as"
618+
path = "exercises/conversions/using_as.rs"
619+
mode = "compile"
620+
hint = """
621+
Use the `as` operator to cast one of the operands in the last line of the
622+
`average` function into the expected return type."""
623+
624+
[[exercises]]
625+
name = "from_into"
626+
path = "exercises/conversions/from_into.rs"
627+
mode = "test"
628+
hint = """
629+
Follow the steps provided right before the `From` implementation"""
630+
631+
[[exercises]]
632+
name = "try_from_into"
633+
path = "exercises/conversions/try_from_into.rs"
634+
mode = "test"
635+
hint = """
636+
Follow the steps provided right before the `From` implementation.
637+
You can also use the example at https://doc.rust-lang.org/std/convert/trait.TryFrom.html"""
638+
639+
[[exercises]]
640+
name = "as_ref_mut"
641+
path = "exercises/conversions/as_ref_mut.rs"
642+
mode = "test"
643+
hint = """
644+
Add AsRef<str> as a trait bound to the functions."""
645+
646+
[[exercises]]
647+
name = "from_str"
648+
path = "exercises/conversions/from_str.rs"
649+
mode = "test"
650+
hint = """
651+
If you've already solved try_from_into.rs, then this is almost a copy-paste.
652+
Otherwise, go ahead and solve try_from_into.rs first."""

0 commit comments

Comments
 (0)