Skip to content

Commit

Permalink
23 type conversions using "as" and "std::convert" module
Browse files Browse the repository at this point in the history
  • Loading branch information
chiffonng committed Jun 27, 2024
1 parent 2c177a4 commit 19d30e8
Show file tree
Hide file tree
Showing 6 changed files with 101 additions and 22 deletions.
4 changes: 1 addition & 3 deletions exercises/23_conversions/1_using_as.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,9 @@
// 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()
total / values.len() as f64 // Total is f64, so we need to cast values.len() (type usize) to f64
}

fn main() {
Expand Down
31 changes: 27 additions & 4 deletions exercises/23_conversions/2_from_into.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@ impl Default for Person {
}
}


// Your task is to complete this implementation in order for the line `let p1 =
// 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
Expand All @@ -41,10 +40,34 @@ impl Default for Person {
// 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 from(s: &str) -> Person {
let parts: Vec<&str> = s.split(',').collect();

// Attempt to parse the name and age from the string
// https://doc.rust-lang.org/book/ch06-03-if-let.html
if let [name, age_str] = parts.as_slice() {
// If the name is empty, return the default person
if name.is_empty() {
return Person::default();
}
// If the converted age is valid, return the person
if let Ok(age) = age_str.parse::<usize>() {
return Person {
name: name.to_string(),
age,
};
}
}
// Otherwise, return the default person
return Person::default();
}
}
/// Convert a Person struct back into a String
impl Into<String> for Person {
fn into(self) -> String {
format!("{},{}", self.name, self.age)
}
}

fn main() {
Expand Down
20 changes: 18 additions & 2 deletions exercises/23_conversions/3_from_str.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,6 @@ enum ParsePersonError {
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
Expand All @@ -48,6 +46,24 @@ enum ParsePersonError {
impl FromStr for Person {
type Err = ParsePersonError;
fn from_str(s: &str) -> Result<Person, Self::Err> {
if s.is_empty() {
return Err(ParsePersonError::Empty);
}
let parts: Vec<&str> = s.split(',').collect();
if parts.len() != 2 {
return Err(ParsePersonError::BadLen);
}
let name = parts[0];
if name.is_empty() {
return Err(ParsePersonError::NoName);
}
let age = parts[1]
.parse::<usize>()
.map_err(ParsePersonError::ParseInt)?;
Ok(Person {
name: name.to_string(),
age,
})
}
}

Expand Down
40 changes: 40 additions & 0 deletions exercises/23_conversions/4_try_from_into.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,25 +36,65 @@ enum IntoColorError {
// 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.
fn is_valid_rgb_value(value: i16) -> bool {
(0..=255).contains(&value)
}

// Tuple implementation
impl TryFrom<(i16, i16, i16)> for Color {
type Error = IntoColorError;
fn try_from(tuple: (i16, i16, i16)) -> Result<Self, Self::Error> {
if is_valid_rgb_value(tuple.0) && is_valid_rgb_value(tuple.1) && is_valid_rgb_value(tuple.2)
{
// Cannot loop through tuple, so we need to check each value separately
Ok(Color {
red: tuple.0 as u8,
green: tuple.1 as u8,
blue: tuple.2 as u8,
})
} else {
Err(Self::Error::IntConversion)
}
}
}

// Array implementation
impl TryFrom<[i16; 3]> for Color {
type Error = IntoColorError;
fn try_from(arr: [i16; 3]) -> Result<Self, Self::Error> {
// Iterate through the array and check if each value is in the 0..=255 range
// Use `all` method to check all values
// Function `is_valid_rgb_value` owns the value "c", so we need to use a reference "&c"
if arr.iter().all(|&c| is_valid_rgb_value(c)) {
Ok(Color {
red: arr[0] as u8,
green: arr[1] as u8,
blue: arr[2] as u8,
})
} else {
Err(Self::Error::IntConversion)
}
}
}

// Slice implementation
impl TryFrom<&[i16]> for Color {
type Error = IntoColorError;
fn try_from(slice: &[i16]) -> Result<Self, Self::Error> {
// Slice is a dynamic type, so we need to check the length
if slice.len() != 3 {
return Err(Self::Error::BadLen);
}
// Iterate through the slice and check if each value is in the 0..=255 range
if slice.iter().all(|&c| is_valid_rgb_value(c)) {
Ok(Color {
red: slice[0] as u8,
green: slice[1] as u8,
blue: slice[2] as u8,
})
} else {
Err(Self::Error::IntConversion)
}
}
}

Expand Down
17 changes: 9 additions & 8 deletions exercises/23_conversions/5_as_ref_mut.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,24 +8,25 @@
// hint.

// I AM NOT DONE
use std::convert::{AsMut, AsRef};

// 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 {
// 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.
// TODO: Add the AsRef trait appropriately as a trait bound.
fn char_counter<T>(arg: T) -> usize {
// 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 as_mut().
// TODO: Add the appropriate trait bound.
fn num_sq<T>(arg: &mut T) {
// TODO: Implement the function body.
???
// Add the appropriate trait bound.
fn num_sq<T: AsMut<u32>>(num: &mut T) {
let n = num.as_mut();
*n = (*n) * (*n);
}

#[cfg(test)]
Expand Down
11 changes: 6 additions & 5 deletions exercises/23_conversions/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,19 @@

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.
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`](1_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)
- `From` and `Into` covered in [`from_into`](2_from_into.rs)
- `FromStr` covered in [`from_str`](3_from_str.rs)
- `TryFrom` and `TryInto` covered in [`try_from_into`](4_try_from_into.rs)
- `AsRef` and `AsMut` covered in [`as_ref_mut`](5_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.
These should be the main ways **_within the standard library_** to convert data into your desired types.

## Further information

Expand Down

0 comments on commit 19d30e8

Please sign in to comment.