15.3Generic structs and enums

Last updated June 13, 2026

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:

#[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:

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) is defined like this:

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) 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:

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, 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:

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) 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 }?

Show solution

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.

Question #2

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

Show solution

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 Strings. They've been generics all along; chapter 11 just used them before naming the machinery.

Question #3

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

Show solution

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) 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.

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>.