- The core concept of generics is abstraction over types. They let us write one piece of code to operate with any data type without repeating ourselves to write separate versions for each type. At the compile time, Rust ensures the type safety and generates an optimized code for each concrete type used in the program.
- 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
With One Generic Type
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); // (i32, i32)
println!("{b:?}"); // (0, 1)
let c = Point { x: false, y: true }; // a: Point<bool>
let d = to_tuple(c.x, c.y); // (bool, bool)
println!("{d:?}"); // (false, true)
}
With Multiple Generic Types
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); // (bool, u8)
println!("{b:?}"); // (true, 1)
}
On some occasions, the compiler cannot inter the type, and we have to specify the type when using the generic type. By the way, 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 inter the type here. We have to specify the type.
let b = Data::KeyValue(1, true); // The compiler can infer the type; i32, bool
println!("{a:?}"); // Value(true)
println!("{b:?}"); // KeyValue(1, true)
}
π¨βπ« Before going to the next…
Option and Result
π This is a quick reference to
OptionandResultas enums. Please donβt worry too much about them for now, as we will discuss them in detail later in Error HandlingβOption & Result.Many languages use
null\nil\undefinedtypes to represent empty outputs, and Exceptions to handle errors. Rust skips using both, especially to prevent issues like null pointer exceptions, sensitive data leakages through exceptions, etc. Option and Result types are two special generic enums defined in Rustβs standard library to deal with these cases.// An output can have either Some value or no value/ None. enum Option<T> { // T is a generic and it can contain any type of value. Some(T), None, } // A result can represent either success/ Ok or failure/ Err. enum Result<T, E> { // T and E are generics. T can contain any type of value, E can be any error. Ok(T), Err(E), }An optional value can have either Some value or no value/ None β possibility of absence
A result can represent either success/ Ok or failure/ Err β possibility of failure
Option
struct Task { title: String, assignee: Option<Person>, // π‘ Instead of `assignee: Person`, we use `assignee: Option<Person>` as the assignee can be `None`. }fn get_id_by_username(username: &str) -> Option<usize> { // π‘ Instead of setting return type as `usize`, set it `Option<usize>` // if username can be found in the system, return userId return Some(userId); // π‘ Instead of return userId, return Some(userId) // else None // π‘ The last return statement no need `return` keyword and ending `;` } fn main() { let username = "anonymous"; match get_id_by_username(username) { // π‘ We can use pattern matching to catch the relevant return type (Some/None) None => println!("User not found"), Some(i) => println!("User Id: {}", i), } }Result
fn get_word_count_from_file(file_name: &str) -> Result<u32, &str> { // π‘ Instead of setting return type as `u32`, set it `Result<u32, &str>` // if the file is not found on the system, return error return Err("File can not be found!"); // π‘ Instead panic/ break when the file can not be found; return Err(something) // else, count and return the word count Ok(word_count) // π‘ Instead of return `word_count`, return `Ok(word_count)` // π‘ The last return statement no need `return` keyword and ending `;` } fn main() { let mut file_name = "file_a"; match get_word_count_from_file(file_name) { // π‘ We can use pattern matching to catch the relevant return type (Ok/Err) Ok(i) => println!("Word Count: {}", i), Err(e) => println!("Error: {}", e) } }