18 Rustlings Quiz 3 Solution
Quiz3.rs
It's time to revisit what we've learned so far. In this case, we're focusing on generics and traits in Rust. Let's dive into the exercise.
// quiz3.rs
// This quiz tests:
// - Generics
// - Traits
// An imaginary magical school has a new report card generation system written in Rust!
// Currently the system only supports creating report cards where the student's grade
// is represented numerically (e.g. 1.0 -> 5.5).
// However, the school also issues alphabetical grades (A+ -> F-) and needs
// to be able to print both types of report card!
// Make the necessary code changes in the struct ReportCard and the impl block
// to support alphabetical report cards. Change the Grade in the second test to "A+"
// to show that your changes allow alphabetical grades.
// Execute `rustlings hint quiz3` or use the `hint` watch subcommand for a hint.
// I AM NOT DONE
pub struct ReportCard {
pub grade: f32,
pub student_name: String,
pub student_age: u8,
}
impl ReportCard {
pub fn print(&self) -> String {
format!("{} ({}) - achieved a grade of {}",
&self.student_name, &self.student_age, &self.grade)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn generate_numeric_report_card() {
let report_card = ReportCard {
grade: 2.1,
student_name: "Tom Wriggle".to_string(),
student_age: 12,
};
assert_eq!(
report_card.print(),
"Tom Wriggle (12) - achieved a grade of 2.1"
);
}
#[test]
fn generate_alphabetic_report_card() {
// TODO: Make sure to change the grade here after you finish the exercise.
let report_card = ReportCard {
grade: 2.1,
student_name: "Gary Plotter".to_string(),
student_age: 11,
};
assert_eq!(
report_card.print(),
"Gary Plotter (11) - achieved a grade of A+"
);
}
}
Our task here is to allow both numeric and alphabetical grades, currently we can only print numeric grades correctly. So we how can we fix this. Generics of course!
Quiz3.rs errors
When we run the tests, we find that one test passes while the other fails. Here's the output:
⚠️ Testing of exercises/quiz3.rs failed! Please try again. Here's the output:
running 2 tests
test tests::generate_numeric_report_card ... ok
test tests::generate_alphabetic_report_card ... FAILED
successes:
successes:
tests::generate_numeric_report_card
failures:
---- tests::generate_alphabetic_report_card stdout ----
thread 'tests::generate_alphabetic_report_card' panicked at 'assertion failed: `(left == right)`
left: `"Gary Plotter (11) - achieved a grade of 2.1"`,
right: `"Gary Plotter (11) - achieved a grade of A+"`', exercises/quiz3.rs:57:9
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
failures:
tests::generate_alphabetic_report_card
test result: FAILED. 1 passed; 1 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
Quiz3.rs solution
The issue here is that our ReportCard
struct is not designed to handle both numerical and alphabetical grades. So let's solve this problem by implementing generics.
Our original ReportCard
struct looks like this:
pub struct ReportCard {
pub grade: f32,
pub student_name: String,
pub student_age: u8,
}
Updating the Struct to use Generics
Since the student_name
will always be a String
and the student_age
will always be a u8
, we only need to make the grade
field generic:
pub struct ReportCard<T> {
pub grade: T,
pub student_name: String,
pub student_age: u8,
}
This should do the trick for the struct but now we have to take a look at our impl
block
impl ReportCard {
pub fn print(&self) -> String {
format!("{} ({}) - achieved a grade of {}",
&self.student_name, &self.student_age, &self.grade)
}
}
Updating the Implementation Block
Next, we update the impl
block to accommodate the generic type T
:
impl<T> ReportCard<T> {
// rest of code
}
This let's the compiler know that we are using generics in this block of code. Alright let's save the changes and see what happens!
Uh oh. Problems
So it seems like we are not quite done yet. Since we are now using generics in our impl
block the compiler is telling us that we cannot format the grade
portion of this code on line 29 at : &self.grade
, but luckily it has a suggestion. The compiler suggests restricting T
to types that implement Display
:
⚠️ Compiling of exercises/quiz3.rs failed! Please try again. Here's the output:
error[E0277]: `T` doesn't implement `std::fmt::Display`
--> exercises/quiz3.rs:29:52
|
29 | &self.student_name, &self.student_age, &self.grade
| ^^^^^^^^^^^ `T` cannot be formatted with the default formatter
|
= note: in format strings you may be able to use `{:?}` (or {:#?} for pretty-print) instead
= note: this error originates in the macro `$crate::__export::format_args` which comes from the expansion of the macro `format` (in Nightly builds, run with -Z macro-backtrace for more info)
help: consider restricting type parameter `T`
|
25 | impl<T: std::fmt::Display> ReportCard<T> {
| +++++++++++++++++++
error: aborting due to previous error
Okay so let's fix that our impl
block now looks like this:
impl<T> ReportCard<T> {
pub fn print(&self) -> String {
format!(
"{} ({}) - achieved a grade of {}",
&self.student_name, &self.student_age, &self.grade
)
}
}
If we save now we should be..doh. Our test fails.
running 2 tests
test tests::generate_numeric_report_card ... ok
test tests::generate_alphabetic_report_card ... FAILED
successes:
successes:
tests::generate_numeric_report_card
failures:
---- tests::generate_alphabetic_report_card stdout ----
thread 'tests::generate_alphabetic_report_card' panicked at 'assertion failed: `(left == right)`
left: `"Gary Plotter (11) - achieved a grade of 2.1"`,
right: `"Gary Plotter (11) - achieved a grade of A+"`', exercises/quiz3.rs:59:9
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
failures:
tests::generate_alphabetic_report_card
Resolving Test Failures
After making these changes, the test for alphabetical grades still fails. The issue is that we forgot to update the grade in the generate_alphabetic_report_card()
test function from 2.1
to A+
.
Final Working Code
Here's the complete, working code for reference:
pub struct ReportCard<T> {
pub grade: T,
pub student_name: String,
pub student_age: u8,
}
impl<T: std::fmt::Display> ReportCard<T> {
pub fn print(&self) -> String {
format!(
"{} ({}) - achieved a grade of {}",
&self.student_name, &self.student_age, &self.grade
)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn generate_numeric_report_card() {
let report_card = ReportCard {
grade: 2.1,
student_name: "Tom Wriggle".to_string(),
student_age: 12,
};
assert_eq!(
report_card.print(),
"Tom Wriggle (12) - achieved a grade of 2.1"
);
}
#[test]
fn generate_alphabetic_report_card() {
let report_card = ReportCard {
grade: "A+",
student_name: "Gary Plotter".to_string(),
student_age: 11,
};
assert_eq!(
report_card.print(),
"Gary Plotter (11) - achieved a grade of A+"
);
}
}
Notes
I used the code below, in Rust playgrounds essentially letting me print the report card which then allowed me to have errors when trying to compile and better figure out what I needed to be doing by seeing the printout instead of just having the tests as the compiler gave me the error that T<: std::fmt::Debug>
was missing from the impl
signature.
Note this is different that the
T<: std::fmt::Display>
used in solving the exercise.
#[derive(Debug)]
pub struct ReportCard<T> {
pub grade: T,
pub student_name: String,
pub student_age: u8,
}
impl<T: std::fmt::Display> ReportCard<T> {
pub fn print(&self) -> String {
format!("{} ({}) - achieved a grade of {:?}",
&self.student_name, &self.student_age, &self.grade)
}
}
fn main() {
fn generate_numeric_report_card() {
let report_card = ReportCard {
grade: 2.1,
student_name: "Tom Wriggle".to_string(),
student_age: 12,
};
println!("{:?}", report_card);
}
generate_numeric_report_card();
fn generate_alphabetic_report_card() {
let report_card = ReportCard {
grade: "A+",
student_name: "Gary Plotter".to_string(),
student_age: 12,
};
println!("{:?}", report_card);
}
generate_alphabetic_report_card();
}
Conclusion
In this exercise, we learned how to use generics to make our ReportCard
struct more flexible, allowing it to handle both numerical and alphabetical grades. We also saw how to constrain our generic types to those that implement specific traits, in this case, the Display
trait. This ensures that we can format the grade regardless of its type.