| title | Generics |
|---|---|
| slug | generics |
- Rust let us write one piece of code to operate with multiple data type via generics, without repeating ourselves to write separate versions for each type.
- Use an uppercase letter (
T,U, ...) or aPascalCaseidentifier for the data type.- Instead of
x: u8we usex: T. - Inform the compiler that
Tis a generic type by adding<T>at first.
- Instead of
Tip
- Rust generics represent an abstraction over types.
- As assembly language is inherently non-generic, the Rust compiler uses monomorphization to generate distinct, concrete machine code for each type used.
struct Point<T> {
x: T,
y: T,
}
fn to_tuple<T>(x: T, y: T) -> (T, T) {
(x, y)
}
fn main() {
let a = Point { x: 0, y: 1 }; // a: Point<i32>
let b = to_tuple(a.x, a.y); // b: (i32, i32)
let c = Point { x: false, y: true }; // c: Point<bool>
let d = to_tuple(c.x, c.y); // d: (bool, bool)
println!("{b:?}"); // (0, 1)
println!("{d:?}"); // (false, true)
}
// 💡 We can achieve the same functionality by destructures.
// let b = {
// let Point { x, y } = a;
// (x, y)
// };struct Point<T, U> {
x: T,
y: U,
}
fn to_shuffled_tuple<T, U>(x: T, y: U) -> (U, T) {
(y, x)
}
fn main() {
let a = Point { x: 1u8, y: true }; // a: Point<u8, bool>
let b = to_shuffled_tuple(a.x, a.y); // b: (bool, u8)
println!("{b:?}"); // (true, 1)
}
// 💡 We can achieve the same functionality by destructures.
// let b = {
// let Point { x, y } = a;
// (y, x)
// };enum Data<K, V> {
Value(V),
KeyValue(K, V),
}
fn main() {
let data = vec![
Data::KeyValue("Steve".to_string(), 10),
Data::Value(20),
Data::KeyValue("Tom".to_string(), 30),
Data::Value(40),
Data::KeyValue("Mike".to_string(), 50),
];
for item in data {
match item {
Data::KeyValue(k, v) => println!("{k}: {v}"),
Data::Value(v) => println!("Unknown: {v}"),
}
}
}Tip
On some occasions, the compiler cannot infer the type, and we have to specify the type when using the generic type.
However, it's good practice to specify the type on variables when using a generic implementation.
#[derive(Debug)]
enum Data<K, V> {
Value(V),
KeyValue(K, V),
}
fn main() {
let a: Data<(), bool> = Data::Value(true); // ⭐️ The compiler can not infer the type here
let b = Data::KeyValue(1, true); // The compiler can infer the type here
println!("{a:?}"); // Value(true)
println!("{b:?}"); // KeyValue(1, true)
}-
OptionandResult[!recap] This is a quick reference to
OptionandResultas generic enums. So, please don’t worry too much about them for now. We will discuss them in detail under Error Handling:OptionandResult.Many programming languages use exceptions to handle errors and
null\nil\undefinedtypes to handle missing values. Historically, this decision has led to severe runtime issues (such as null pointer exceptions) and security vulnerabilities (sensitive data leakages, through error traces and exceptions). Rust skip both and provide two special generic enums defined in its standard library to prevent these issues.-
Option- Represents the potential absence of a value.
- The value can have either some value/
Someor no value/None.
enum Option<T> { Some(T), None, }
-
Result- Represents the outcome of a fallible operation.
- The result can have either success/
Okor failure/Err.
enum Result<T, E> { Ok(T), Err(E), }
-