---
title: "15.3 Generic structs and enums"
description: "Structs and enums can take type parameters too; the reveal that Option and Vec were generic all along, plus type aliases and a glance at const generics."
url: "https://learnrust.net/chapter-15/generic-structs-and-enums/"
last_updated: "2026-06-13"
---

# 15.3 Generic structs and enums

Functions aren't the only things that can be generic. Structs and enums take type parameters too, and once you see the syntax, a quiet revelation lands: `Option<T>` and `Vec<T>`, which you've used since chapter 11, were generic types all along. That `<T>` you've been reading as part of their names is the same `<T>` from the last two lessons.

## Generic structs

A struct declares type parameters after its name and uses them as field types:

```rust
#[derive(Debug)]
struct Point<T> {
    x: T,
    y: T,
}

fn main() {
    let integer = Point { x: 5, y: 10 };
    let float = Point { x: 1.5, y: 4.2 };
    println!("{integer:?}");
    println!("{float:?}");
}
```

```
Point { x: 5, y: 10 }
Point { x: 1.5, y: 4.2 }
```

`Point<T>` is generic: both fields are type `T`, so `Point { x: 5, y: 10 }` is a `Point<i32>` and `Point { x: 1.5, y: 4.2 }` is a `Point<f64>`. The compiler infers the type parameter from the values, just as it did for functions. Because both fields are `T`, they must be the *same* type; `Point { x: 5, y: 1.5 }` (an `i32` and an `f64`) would be refused. If you want them to differ, use two parameters:

```rust
struct Point<T, U> {
    x: T,
    y: U,
}
```

Now `Point { x: 5, y: 1.5 }` is a `Point<i32, f64>` and compiles. Use one parameter when the fields share a type, several when they don't.

## Generic enums, and the big reveal

Enums take type parameters the same way, and here's where it pays off. The two most important enums in the language, which you've used for chapters, are generic. `Option<T>` (lesson [11.3](https://learnrust.net/chapter-11/option/)) is *defined* like this:

```rust
enum Option<T> {
    Some(T),
    None,
}
```

That's a generic enum. `Some(T)` carries a value of the type parameter `T`, which is why `Option<i32>` holds an `i32` and `Option<String>` holds a `String`. The `<T>` you've been reading next to `Option` was never special syntax for `Option`; it's the ordinary type parameter from this chapter. `Result<T, E>` (lesson [12.2](https://learnrust.net/chapter-12/result/)) is the same idea with *two* parameters, one for success and one for the error. And `Vec<T>`, the growable list, is a generic struct: a `Vec<i32>` is a vector of `i32`, a `Vec<String>` a vector of `String`.

> **Key insight**
>
> You've been using generics since chapter 11 without the vocabulary. Every `Option<T>`, `Result<T, E>`, and `Vec<T>` is a generic type, and the angle brackets were type parameters the whole time. This is the broad-then-deep teaching from the course's design: you used these types first, got comfortable, and now the machinery underneath them turns out to be something you can build yourself. Nothing about them is magic; they're structs and enums with a `<T>`.

## Type aliases

A related convenience: when a generic type's full spelling gets long and you write it repeatedly, you can give it a shorter name with a **type alias**, the `type` keyword. It doesn't create a new type, just a synonym:

```rust
type Pair = (i32, i32);
type Lookup = std::collections::HashMap<String, Vec<i32>>;

fn main() {
    let p: Pair = (3, 4);   // exactly the same as (i32, i32)
    println!("{p:?}");
}
```

```
(3, 4)
```

`Pair` and `(i32, i32)` are interchangeable; the alias just saves typing and adds a meaningful name. You'll see this most for taming verbose generic types like that `HashMap<String, Vec<i32>>`, and the standard library uses it heavily: `std::io::Result<T>` is a type alias for `Result<T, std::io::Error>`, which is why I/O functions seem to return a one-parameter `Result`. Aliases name a type; they don't restrict or wrap it (that's the newtype pattern from lesson [10.4](https://learnrust.net/chapter-10/tuple-structs-and-unit-structs/), which *does* make a distinct type).

## A glance at const generics

One more variety, mentioned so the syntax isn't a surprise later. Generics usually range over *types*, but they can also range over *constant values*, most commonly array lengths. A function generic over an array's size looks like this:

```rust
fn first<const N: usize>(arr: [i32; N]) -> i32 {
    arr[0]
}
```

`<const N: usize>` is a **const generic**: `N` is a compile-time number, not a type, so this function works for arrays of any fixed length. You'll rarely write const generics yourself early on, but they're why `[T; N]` arrays (lesson [18.7](https://learnrust.net/chapter-18/arrays-and-slices-revisited/)) can be handled generically across lengths. File it away; it's the same generics idea applied to numbers instead of types.

## Quiz time

**Question #1**

In `struct Wrapper<T> { value: T }`, what type is `Wrapper { value: "hi" }`, and what type is `Wrapper { value: 5 }`?

<details class="solution">
<summary>Show solution</summary>

`Wrapper { value: "hi" }` is a `Wrapper<&str>` (the literal is a `&str`), and `Wrapper { value: 5 }` is a `Wrapper<i32>`. The compiler infers the type parameter from the field's value, the same inference as generic functions.

</details>

**Question #2**

`Option<T>` and `Vec<T>` are examples of what, and what does the `<T>` mean in them?

<details class="solution">
<summary>Show solution</summary>

They're generic types (a generic enum and a generic struct, respectively). The `<T>` is a type parameter, exactly as in the generic functions of this chapter: `Option<i32>` holds an `i32`, `Vec<String>` holds `String`s. They've been generics all along; chapter 11 just used them before naming the machinery.

</details>

**Question #3**

What's the difference between a type alias (`type Id = u64;`) and a newtype (`struct Id(u64);`)?

<details class="solution">
<summary>Show solution</summary>

A type alias is just a synonym: `Id` and `u64` are interchangeable, and the compiler treats them as the same type. A newtype (lesson [10.4](https://learnrust.net/chapter-10/tuple-structs-and-unit-structs/)) creates a *distinct* type that can't be mixed with `u64` without unwrapping, giving you the compiler-enforced safety of keeping look-alikes apart. Use an alias to shorten a long name, a newtype to create a real, separate type.

</details>

Generic structs hold data of type `T`; the next lesson gives them *behavior* with generic `impl` blocks, and shows how to write methods that exist only for specific instantiations like `Point<f64>`.

## Sitemap

See the full [sitemap](https://learnrust.net/sitemap.md) for all pages.
