[rustling generics3] Did I solve this one the right way

Hello,

I wonder if I did this one the right way . the generics on the imp looks wierd to me.

// 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 to support alphabetical report cards, thereby making the second
// test pass.

// I AM NOT DONE
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() {
        // TODO: Make sure to change the grade here after you finish the exercise.
        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+");
    }
}
2 Likes

It’s a perfectly correct and acceptable solution. Everything I say below is fundamentally about style, so use your own judgement.

Usually, when the entire impl is going to be constrained like that, I’ll also include the constraint on the struct definition; there’s not much use in making an object that can’t do anything:

use std::fmt::Display;

pub struct ReportCard<T: Display> {
    pub grade: T,
    pub student_name: String,
    pub student_age: u8,
}

impl <T: Display> ReportCard<T> {
    pub fn print(&self) -> String {
        format!("{} ({}) - achieved a grade of {}", &self.student_name, &self.student_age, &self.grade)
    }
}

Alternatively, it may make sense to only restrict the print function:

impl <T> ReportCard<T> {
    pub fn print(&self) -> String where T: Display {
        format!("{} ({}) - achieved a grade of {}", &self.student_name, &self.student_age, &self.grade)
    }
}
2 Likes

Thanks,

The last code seems to be :

impl <T:Display> ReportCard<T> {
    pub fn print(&self) -> String where T: Display {
        format!("{} ({}) - achieved a grade of {}", &self.student_name, &self.student_age, &self.grade)
    }
}

otherwise the compiler is not happy

or I have to change it to this :

pub struct ReportCard<T> {
    pub grade: T,
    pub student_name: String,
    pub student_age: u8,
}

impl <T> ReportCard<T> {
    pub fn print(&self) -> String where T: Display {
        format!("{} ({}) - achieved a grade of {}", &self.student_name, &self.student_age, &self.grade)
    }
}
1 Like

Right; the two options mean slightly different things, and different situations might call for each of them:

  • The first way prevents ReportCard from ever existing with a T that can’t be displayed.
  • The second always allows the ReportCard to exist, but only defines the print function if T can be displayed.

Your original solution is halfway in-between: any T can be used to create a ReportCard, but the all functions in the impl block will only be defined for displayable Ts. Personally, I usually find one of the other two options clearer.

1 Like

yep, i find the second cleaner. It tells more what is really happening.

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.