Generic Structs
🎯 Python's classes can hold any type without declaring it:
class Container:
def __init__(self, item):
self.item = item
Container(42)
Container("hello")
Container([1, 2, 3])
No type declarations needed — item can be anything. With type hints, you can be more specific:
from typing import Generic, TypeVar
T = TypeVar("T")
class Container(Generic[T]):
def __init__(self, item: T):
self.item = item
But Python's generics are only for type checkers. At runtime, Container[int] and Container[str] are the same class.
Rust's generic structs are different — they're real, distinct types at compile time:
struct Container<T> {
item: T,
}
// Container<i32> and Container<String> are different types
// The compiler generates optimized code for each
Defining generic structs
The <T> after the struct name declares a type parameter. Use it in fields:
struct Container<T> {
item: T,
}
struct Entry<K, V> {
key: K,
val: V,
}
Container<T> has one type parameter — both occurrences must be the same type. Entry<K, V> has two — key and val can be different types.
Implementing methods with impl
To add methods, you repeat the type parameter on impl:
impl<T> Container<T> {
fn new(item: T) -> Self {
Container { item }
}
fn unwrap(self) -> T {
self.item
}
}
The impl<T> says "this implementation works for any T." Self is shorthand for Container<T> — the concrete type being implemented.
self vs &self: consuming vs borrowing
Notice unwrap takes self (not &self). This means it consumes the container:
let c = Container::new(42);
let item = c.unwrap(); // c is consumed, item is 42
// c can't be used anymore — it was moved
In Python, every method gets self as a reference — the object continues to exist. In Rust, self (without &) takes ownership, destroying the original. This is the "into" naming convention: consuming the struct to extract the inner value.
Compare:
- &self — borrows, original survives (like Python's self)
- self — consumes, original is gone (no Python equivalent)
Swapping type parameters
A method on Entry<K, V> can return Entry<V, K> — the type parameters reversed:
impl<K, V> Entry<K, V> {
fn flip(self) -> Entry<V, K> {
Entry {
key: self.val,
val: self.key,
}
}
}
The return type Entry<V, K> is a different concrete type than Entry<K, V>. The compiler tracks this — after flipping, the type system knows key is now type V and val is type K.
You've already used generic structs
Vec<T>, Option<T>, Result<T, E>, HashMap<K, V> — Rust's most important types are all generic structs. Building your own follows the same pattern.
Login to see the full task and start coding.
Topics
This is a premium exercise
Log in to unlock the full exercise and start coding.
Login to access this exercise