16 Rustlings Generics Solution

Generics

From the README

Generics is the topic of generalizing types and functionalities to broader cases. This is extremely useful for reducing code duplication in many ways, but can call for rather involving syntax. Namely, being generic requires taking great care to specify over which types a generic type is actually considered valid. The simplest and most common use of generics is for type parameters.

Further information

Generics1.rs

// This shopping list program isn't compiling!
// Use your knowledge of generics to fix it.

// Execute `rustlings hint generics1` or use the `hint` watch subcommand for a hint.

// I AM NOT DONE

fn main() {
    let mut shopping_list: Vec<?> = Vec::new();
    shopping_list.push("milk");
}

Our instructions tells to make this code compile using generics. We clearly have a question mark out of place, so what do we put there? Let's first look at our errors to see if we get any additional information.

Generics1.rs errors

⚠️  Compiling of exercises/generics/generics1.rs failed! Please try again. Here's the output:
error: expected identifier, found `>`
 --> exercises/generics/generics1.rs:9:33
  |
9 |     let mut shopping_list: Vec<?> = Vec::new();
  |         -----------------       ^ expected identifier
  |         |
  |         while parsing the type for `mut shopping_list`

error: aborting due to previous error

Not much to see here. We see what we already knew. Alright so how do we solve this issue?

Generics1.rs Solution

It seems like the easiest way to solve this exercise is to give the compiler what it's asking for, the identifier which in this case should be a &str like this:

fn main() {
    let mut shopping_list: Vec<&str> = Vec::new();
    shopping_list.push("milk");
}

By specifying &str as the type parameter for the Vec<T>, we are utilizing Rust's generics mechanism. In Rust, Vec<T> is a generic type where T can be any type. In our solution, T is explicitly set to &str, making the type of the vector Vec<&str>. This demonstrates the power and flexibility of generics, allowing us to define a collection that can hold specific types of data, in this case, string slices.

and with that we are compiling!

Generics2.rs

// This powerful wrapper provides the ability to store a positive integer value.
// Rewrite it using generics so that it supports wrapping ANY type.

// Execute `rustlings hint generics2` or use the `hint` watch subcommand for a hint.

// I AM NOT DONE

struct Wrapper {
    value: u32,
}

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

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn store_u32_in_wrapper() {
        assert_eq!(Wrapper::new(42).value, 42);
    }

    #[test]
    fn store_str_in_wrapper() {
        assert_eq!(Wrapper::new("Foo").value, "Foo");
    }
}

In this exercise we are being asked to refactor this code to be able to handle any type instead of just positive integers.

Generics2.rs Errors

⚠️  Compiling of exercises/generics/generics2.rs failed! Please try again. Here's the output:
error[E0308]: mismatched types
  --> exercises/generics/generics2.rs:27:33
   |
27 |         assert_eq!(Wrapper::new("Foo").value, "Foo");
   |                    ------------ ^^^^^ expected `u32`, found `&str`
   |                    |
   |                    arguments to this function are incorrect
   |
note: associated function defined here
  --> exercises/generics/generics2.rs:11:12
   |
11 |     pub fn new(value: u32) -> Self {
   |            ^^^ ----------

error[E0308]: mismatched types
  --> exercises/generics/generics2.rs:27:9
   |
27 |         assert_eq!(Wrapper::new("Foo").value, "Foo");
   |         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
   |         |
   |         expected `u32`, found `&str`
   |         expected because this is `u32`
   |
   = note: this error originates in the macro `assert_eq` (in Nightly builds, run with -Z macro-backtrace for more info)

error: aborting due to 2 previous errors

In the errors we see problems with our tests, because they are not handling generics so let's fix that

Generic2.rs Solution

Let's start by updating our struct to handle any kind of value, not just a specific type.


struct Wrapper<T> {
    value: T,
}

Here, instead of saying our value is a specific type (like u32 or String), we're using T. Think of T as a placeholder that says, "Hey, I can be any type you want!" It's like when you have a toy where you can swap out the pieces; T is our swappable piece.

By writing <T> next to Wrapper, we're telling Rust, "This Wrapper can work with many types, and we're going to call the type we're working with T." It's a common thing you'll see in Rust, and while T is just a convention (we could use other letters or names), it's widely used to mean "some type."

With this change, our Wrapper is now flexible and can wrap around any type of value.

Now let's work on the impl block or the method which we have to also change to be able to handle generic values.

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

Let's start with the signature where we have to add the <T> to the impl<T> so we can use the generic value in Wrapper. Next we also have to add the T to Wrapper<T> to make it match our struct.

Finally we have to fix our new() function to pass in a generic type and not a u32 as a value:

    pub fn new(value: T) -> Self {

This should be it, let's save and see what happens.

🎉 🎉  The code is compiling! 🎉 🎉

Yes, we're compiling, we've successfully refactored from only accepting u32's to accepting any type.

Conclusion

The exercises on generics provide a hands-on introduction to one of Rust's powerful features: the ability to write code that operates on a variety of types. Generics enable us to write flexible and reusable code without sacrificing performance. Through the exercises, we've seen how to refactor specific types into generic ones, allowing our code to handle a broader range of use cases.