Skip to content

Rust - Generic Types

Generics are one of Rust’s most powerful features. They allow you to write abstract, reusable code that can work with many different data types. This reduces code duplication and improves type safety, all without any runtime performance cost—a principle known as zero-cost abstraction.

We’ve already seen generics in action with types like Vec<T>, Option<T>, and Result<T, E>. The <T> is a type parameter, a placeholder for a concrete type that will be specified when the generic code is used.

You can define your own structs that use generic type parameters. This is useful for creating containers or data structures that can hold values of any type.

// A generic Point struct that can hold any two types for x and y.
struct Point<T, U> {
x: T,
y: U,
}
fn main() {
// An instance where both x and y are integers.
let integer_point = Point { x: 5, y: 10 };
println!("Integer point: x = {}, y = {}", integer_point.x, integer_point.y);
// An instance where x is a float and y is a character.
let mixed_point = Point { x: 1.0, y: 'c' };
println!("Mixed point: x = {}, y = {}", mixed_point.x, mixed_point.y);
}

We can also write functions that operate on generic types. To do this, we need to tell the compiler what capabilities the generic type T must have. We do this using trait bounds.

For example, if we want to write a function that prints a value, the type T must implement the Display trait. The syntax <T: Display> constrains the generic type T to only types that can be displayed.

use std::fmt::Display;
// This function takes a generic type `T` that must implement the `Display` trait.
fn print_item<T: Display>(item: T) {
println!("Here is your item: {}", item);
}
fn main() {
print_item(10_u32); // Works with u32
print_item("Hello, Generics!"); // Works with &str
print_item(3.14); // Works with f64
}

A trait defines a set of methods that a type must implement. It’s similar to an interface in other languages. Traits are how we achieve abstraction and polymorphism in Rust. They allow different types to share the same behavior.

Let’s define a Summary trait that provides a summary of an object’s data.

// 1. Define the Trait
pub trait Summary {
// An abstract method; implementing types must provide the body.
fn summarize_author(&self) -> String;
// A default method; implementing types can use or override this.
fn summarize(&self) -> String {
format!("(Read more from {}...)", self.summarize_author())
}
}
// 2. Define our Structs
pub struct NewsArticle {
pub headline: String,
pub author: String,
}
pub struct Tweet {
pub username: String,
pub content: String,
}
// 3. Implement the Trait for our Structs
impl Summary for NewsArticle {
fn summarize_author(&self) -> String {
format!("@{}", self.author)
}
}
impl Summary for Tweet {
fn summarize_author(&self) -> String {
format!("@{}", self.username)
}
// We can also override the default method
fn summarize(&self) -> String {
format!("{}: {}", self.username, self.content)
}
}
// 4. Use the trait
fn main() {
let tweet = Tweet {
username: String::from("rustacean"),
content: String::from("I love traits!"),
};
let article = NewsArticle {
headline: String::from("Rust 1.70 Released!"),
author: String::from("The Rust Team"),
};
println!("New tweet: {}", tweet.summarize());
println!("New article: {}", article.summarize());
}

For many common traits in the standard library (like Debug for printing, Clone for copying, PartialEq for comparison), you don’t need to write the implementation by hand. Rust provides the #[derive] attribute to automatically generate the implementation for you. This is a huge time-saver and a very common practice in Rust.

// Automatically implement Debug, Clone, and PartialEq for this struct.
#[derive(Debug, Clone, PartialEq)]
struct Rectangle {
width: u32,
height: u32,
}
fn main() {
let r1 = Rectangle { width: 30, height: 50 };
let r2 = r1.clone(); // We can clone it because of `#[derive(Clone)]`
// We can print it with {:?} because of `#[derive(Debug)]`
println!("Rectangle 1: {:?}", r1);
// We can compare them because of `#[derive(PartialEq)]`
println!("Are they equal? {}", r1 == r2);
}