Generic Types in Functions and Structs

Rust allows you to write flexible and reusable code by using generic types in your functions and structs. You define generics with angle brackets like <T>, allowing your code to work across many types while staying safe and fast.

A big shift from JavaScript to Rust is thinking about data types upfront. In JavaScript, you might write a function that handles strings, numbers, or arrays without worrying about types. In Rust, you get that same flexibility but with full type safety by using generics. Once I understood this, I stopped copying the same function for every data type and started writing smarter, reusable logic.

What Are Generics?

Generics let you write code that works with any type. Instead of writing the same logic three times for i32, f64, and String, you write it once using a type placeholder.

The placeholder is usually T, but it can be any name. You define it in angle brackets like <T>.

We already saw how Rust enforces strict types in previous topics like Primitive Data Types in Rust and Type Annotations and Inference. Generics extend that with flexibility.

Generic Function Example

This function returns the first element of any slice:

fn first<T>(items: &[T]) -> Option<&T> {
    items.get(0)
}

Here is what each part means:

  • <T> says the function is generic over type T
  • &[T] is a reference to a slice of items of type T
  • Option<&T> means it may return a reference to a T, or None

You can now call this function with a list of numbers or a list of strings:

let numbers = vec![10, 20, 30];
let names = vec!["Alice", "Bob"];

let first_num = first(&numbers);
let first_name = first(&names);

Rust will figure out what T should be based on the input.

Generics in Structs

You can define structs that store any type:

struct Point<T> {
    x: T,
    y: T,
}

You can use this struct with integers, floats, or even strings:

let int_point = Point { x: 3, y: 5 };
let float_point = Point { x: 1.1, y: 2.2 };
let text_point = Point { x: "left", y: "right" };

Want different types for x and y? Use more than one type parameter:

struct Mixed<T, U> {
    x: T,
    y: U,
}

Now you can do:

let mixed = Mixed { x: 5, y: "blue" };

Generics in Methods

You can add methods to your generic structs by implementing them like this:

impl<T> Point<T> {
    fn get_x(&self) -> &T {
        &self.x
    }
}

You can also write methods for specific types only:

impl Point<f64> {
    fn distance_from_origin(&self) -> f64 {
        (self.x.powi(2) + self.y.powi(2)).sqrt()
    }
}

This method only works for Point<f64>, not other types.

When to Use Generics

Use generics when:

  • You want your code to work with multiple data types
  • You need to write reusable logic like math operations or data containers
  • You want compile-time safety with flexibility

Avoid generics when:

  • Your logic only makes sense for one specific type
  • You are doing I/O, file operations, or need types with side effects

Summary

Generic types in Rust help you write clean, reusable, and safe code without duplication. Whether you are working with functions, structs, or methods, generics let you stay flexible without giving up type safety. If you have already read about Ownership or Functions in Rust, you will see how generics fit naturally into those patterns.