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.
Rust Intermediate Concepts
- Generic Types in Functions and Structs
- Implementing Traits for Custom Behavior in Rust
- Trait Bounds and Constraints in Rust
- Lifetimes in Rust
- Using Closures (Anonymous Functions) in Rust
- The Iterator Trait and .next() in Rust
- Higher Order Iterator Methods: map, filter, and fold in Rust
- Using impl Trait for Simplicity in Rust
- Advanced Collections: HashSet and BTreeMap in Rust
- Custom Error Types and the Error Trait in Rust
- Option and Result Combinators in Rust
- Writing Unit Tests in Rust
- Integration Testing in Rust
- Logging in Rust with the log Crate
- Cargo Tips and Tricks for Rust Projects
- CLI Argument Parsing with Clap in Rust
- File I/O and File System Operations in Rust
- Running External Commands in Rust
- Make HTTP Requests in Rust with Reqwest
- JSON Serialization and Deserialization in Rust with Serde
- Building a Weather CLI in Rust
- Date and Time Handling in Rust with Chrono
- Using Regular Expressions in Rust with the regex Crate
- Memory Management in Rust
- Understanding Borrow Checker Errors in Rust
- Interacting with Databases in Rust
- Building a Todo List CLI in Rust with SQLite
- Concurrency in Rust