Home > Software design >  How and when to use the generic type parameter in impl block in Rust?
How and when to use the generic type parameter in impl block in Rust?

Time:07-09

I am confused about the generic parameter for the impl keyword in Rust.

To explain what I am confused about, I am currently going through the rustling exercises. I am doing the generics part. The second question which can be seen here is to make the Wrapper struct generic.

Easy, the solution is:

struct Wrapper<T> {
    value: T,
}

impl<T> Wrapper<T> {
    pub fn new(value: T) -> Self {
        Wrapper { value }
    }
}

But I ask myself, why does impl have to also have a type parameter? Why impl<T> Wrapper<T> and not just impl Wrapper<T>?

I could not answer that question but on moving on to the next exercise, which can be seen here there is another generic question, which I solved by having this:

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.to_string())
    }
}

Now I am wondering, why is this right, and the following wrong:

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

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

I think the crux of my problem is, I do not really understand the generics syntax. And where to put the generic type parameter, especially when there is an impl block.

Does anyone have an explanation on how these syntax is supposed to be used?

CodePudding user response:

You could think of it the same way a function takes parameters, and can feed back these parameters in a function call, and it will probably make sense.

A generic type Wrapper<T> is "like" a function that takes a type T, and produces an actual, concrete type Wrapper<T> (even though there is no difference in the syntax, just like there is no difference when you define a function and when you call it to get an actual value out of it).

Similarly, an impl block is "like" a function that may take some parameters, and that produces a given implementation for a given type. That is, impl<T> Wrapper<T> { ... } should be read like define a generic implementation that takes a parameter, T, and that produces an actual, concrete implementation { ... } for the concrete type Wrapper<T>, which, in this case, can be seen as the "function call" rather than the "signature declaration".

But, for instance, you could also choose not to make your implementation generic: impl Wrapper<u32> { ... }, or even to make it generic over several arguments, not use none in the Wrapper<T> "call": impl<T, U> Wrapper<char> { ... } because these type parameters can also be used in the actual implementation.


This being said, we can now look at the syntax more in details. Usually, when you define a "generic block" (that is, a generic impl, or a generic struct, or a generic enum, or ...), the first keyword of that block may also specify some times with the <T, U, ...> syntax, which you may think as in a function definition. Every subsequent usage of these types in the block through the same syntax may be though of as a "function call": you feed these arguments into an other "function".

For this reason, you can only add constraints over these types in the first <...> part of an impl block, not after the type:

impl<T: Display> Wrapper<T> { ... }

is valid, because it means given a type T such that T implements the trait Display, I can provide an implementation for the concrete type Wrapper<T>, but

impl<T: Display> Wrapper<T: Display> { ... }

is not valid, and neither is

impl<T> Wrapper<T: Display> { ... }

because you are putting constrains on a "function call". Just like

fn something(a: usize) {
  something_else(a);
}

is valid, but not

fn something(a: usize) {
  something_else(a: usize)
}

nor

fn something(a) {
  something_else(a: usize)
}
  • Related