Rust Generic Types in Method Definitions
Tags: rust, programming
Published: 2021-04-03 23:48:32

### Intro

If you're new to Rust, reading through the official Rust book (which you definitely should), you may have been confused by the examples in the [In Method Definitions](https://doc.rust-lang.org/book/ch10-01-syntax.html#in-method-definitions) section of Chapter 10.1 like I was. In this article, I'll try to clear up any confusion you may have.

For this article to make sense, you are expected to have already read through the book up to that part, and have a basic understanding of generic data types.

I think that most of the confusion stems from the fact that the topic is introduced using a common, but complex example. I submitted a ticket in the github repo for the book with some suggestions to fix this, but until then, lets explore the topic here.

### Simple Example

We'll start simple. If you have a `struct`, and you want to add methods to it, you would do something like this:

```rust
struct Container {
    field: i32,
}

impl Container {
    fn foo(&self) {
        ...
    }
}
```

If you've read the book up to Chapter 10.1, this should make sense to you.

### Simple Example Using Generic Type

If your `struct` contains a generic type on the other hand, your `impl` MUST also specify a type. Ex:

```rust
struct Container<T> {
    field: T,
}

impl Container<[TYPE]> {

}
```

where `[TYPE]` is the type that your `impl` block will be implemented for.

For example, if you were to do this:


```rust
struct Container<T> {
    field: T,
}

impl Container<i32> {
    fn foo(&self) {
        println!("I exist!");
    }
}
```

That means that you can create an instance of your `struct` using any type, BUT, only instances that use the `i32` type will have the `foo` method.

If we were to create a few instances, we can confirm this:

```rust
let inst1 = Container { field: 'c' };
let inst2 = Container { field: 10 };
let inst3 = Container { field: 12.2 };

inst1.foo(); // Won't work - method doesn't exist
inst2.foo(); // Works fine
inst3.foo(); // Won't work - method doesn't exist
```

These 3 instances are of the same `struct`, but only `inst2` has `i32` as the generic type being passed into it when the instance is created, so it is the only instance that will have the `foo` method.

Another way to say this is that the `[TYPE]` from above is a matcher. All the methods defined in the `impl` block will only be attached to instances that match that type.

If this makes sense to you - great! Read on. If not, read it again, because understanding the idea above is the key to understanding the example in the book.

### Simple Example Using Custom Generic Type

Because rust allows you to define your own types, like a `struct` or an `enum`, the code above is almost exactly the same as the following:

```rust
struct Color {
    red: u8,
    green: u8,
    blue: u8,
}

struct Container<T> {
    field: T,
}

impl Container<Color> {
    fn foo(&self) {
        println!("I exist");
    }
}

fn main() {
    let inst1 = Container {
        field: Color { red: 10, green: 20, blue: 30 },
    };
    let inst2 = Container { field: 10 };
    let inst3 = Container { field: 12.2 };

    inst1.foo(); // This will work
    inst2.foo(); // Won't work - method doesn't exist
    inst3.foo(); // Won't work - method doesn't exist
}
```

Here, we've defined a custom type called `Color`, and our `impl` block for our `Container` struct says that the `foo` method should only be added to instances where the `T` (the generic type) is of type `Color`. This is true for `inst1` (where T is of type `Color`), but not true for `inst2` (where T is of type `i32`) and `inst3` (where T is of type `f64`).

### Common beginner mistake in impl block

Now, knowing all that, what happens if we want the methods in the `impl` block to be available on all instances of our struct, no matter what type it is? 

Your first thought may be to do something like this:

```rust
struct Container<T> {
    field: T,
}

impl Container<T> {
    fn foo(&self) {
        println!("I exist!");
    }
}
```

Unfortunately, this won't quite work the way you might expect. This is almost exactly the same as the example above that uses a custom `Color` type. You are saying that you want `foo` implemented only where the generic type = `T`, but you have not defined type `T` anywhere. The compiler doesn't know that when you wrote `impl Container<T>`, what you meant was a generic type. It tries to find an actual, concrete definition somewhere in this scope for a type `T`, and since that doesn't exist, the code will fail to compile.

Note that while the `struct Container<T>` line does define a generic type called `T`, this definition's scope is only within that struct's body. That type does NOT exist for the `impl` definition to use.

### impl block matching all types

What you need is a way to tell the compiler that your `impl` block should match against all types. To do this, you must define a generic type within the `impl` definition. This looks like the following:

```rust
struct Container<T> {
    field: T,
}

impl<T> Container<T> {
    fn foo(&self) {
        println!("I exist!");
    }
}
```

Here, within the `impl` definition, we have defined a generic type `T`, and later specified that this `impl` block should match any instances that match this generic type, which, without any trait bounds (which you learn about in the following section of the book) is all types. The result is that your `foo` method will be available on all instances of your `struct`, no matter what your generic type `T` ended up being.

Hopefully, this explains why you need to write `<T>` twice within the `impl` line - once to define a generic type, and once to match against it.

### Rules

While working with generic types in `impl` definitions, there are a few rules that are important to remember:

#### Rule 1

The number of matchers in your `impl` block must be the same as the number of generic type args in your `struct` definition

Ex:

```rust
struct Container<T, V> { }
impl Container<i32, i32> { }
```

Since there are 2 generic type args in the `struct` definition, there MUST be 2 in the matcher of the `impl` block.

This won't work:

```rust
struct Container<T, V> { }
impl Container<i32> { }
```

Neither will this:

```rust
struct Container<T, V> { }
impl Container { }
```

#### Rule 2

The number of generic args defined in the `impl` block do NOT need to match the number of generic args defined in the `struct`.

This works (2 generic args in `struct` definition, 0 in `impl` definition):

```rust
struct Container<T, V> { }
impl Container<i32, i32> { }
```

As does this (2 generic args in `struct` definition, 1 in `impl` definition):

```rust
struct Container<T, V> { }
impl<T> Container<T, i32> { }
```

As does this (2 generic args in `struct` definition, 2 in `impl` definition):

```rust
struct Container<T, V> { }
impl<T, V> Container<T, V> { }
```

#### Rule 3

The names of the generic args in the `impl` block do NOT need to match the names of the generic args in the `struct`.

This works fine, and does exactly the same thing as the above example:

```rust
struct Container<T, V> { }
impl<A, B> Container<A, B> { }
```

#### Matching against duplicate types

Besides matching on concrete types, you can also match against instances where both generic types are the same. Ex:

```rust
struct Container<T, V> { }
impl<X> Container<X, X> { }
```

Here, we've defined a single generic type in the `impl` line called `X`, and have stated that this `impl` block will only match against instances where both the generic args are of the same type, no matter what that type happens to be. Again - this doesn't mean you can't create an instance where `T` and `V` are different types - you can - but the methods in this `impl` block will not be available on those instances.

#### Conclusion

The official Rust book is amazing, and introduces a fairly complex language in a very approachable way. This topic is one of the few where the book falls a little short - at least in my opinion. I'm sure that with time, the book will expand on this topic, but until then, I really hope that this article clears things up for people also confused after reading that particular section of the book, like I was when I first read it.