📖 Sometimes, when writing a function or data type, we may want it to work for multiple types of arguments. In Rust, we can do this with generics.
💭 The concept is, instead of declaring a specific data type we use an uppercase letter(or PascalCase identifier). ex, instead of x : u8 we use x : T . but we have to inform to the compiler that T is a generic type(can be any type) by adding <T>
at first.
fn takes_anything<T>(x: T) { // x has type T, T is a generic type
}
fn takes_two_of_the_same_things<T>(x: T, y: T) { // Both x and y has the same type
}
fn takes_two_things<T, U>(x: T, y: U) { // Multiple types
}
struct Point<T> {
x: T,
y: T,
}
fn main() {
let point_a = Point { x: 0, y: 0 }; // T is a int type
let point_b = Point { x: 0.0, y: 0.0 }; // T is a float type
}
// 🔎 When adding an implementation for a generic struct, the type parameters should be declared after the impl as well
// impl<T> Point<T> {
enum Option<T> {
Some(T),
None,
}
enum Result<T, E> {
Ok(T),
Err(E),
}
⭐️ Above Option and Result types are kind of special generic types which are already defined in Rust’s standard library.
- An optional value can have either Some value or no value/ None.
- A result can represent either success/ Ok or failure/ Err
// 01 - - - - - - - - - - - - - - - - - - - - - -
fn get_id_by_username(username: &str) -> Option<usize> {
// if username can be found in the system, set userId
return Some(userId);
// else
None
}
// 💭 So, on the above function, instead of setting return type as usize
// set return type as Option<usize>
// Instead of return userId, return Some(userId)
// else None (💡remember? last return statement no need return keyword and ending ;)
// 02 - - - - - - - - - - - - - - - - - - - - - -
struct Task {
title: String,
assignee: Option<Person>,
}
// 💭 Instead of assignee: Person, we use Option<Person>
// because the task has not been assigned to a specific person
// - - - - - - - - - - - - - - - - - - - - - - -
// When using Option types as return types on functions
// we can use pattern matching to catch the relevant return type(Some/None) when calling them
fn main() {
let username = "anonymous";
match get_id_by_username(username) {
None => println!("User not found"),
Some(i) => println!("User Id: {}", i)
}
}
📖 The Option type is a way to use Rust’s type system to express the possibility of absence. Result expresses the possibility of error.
// - - - - - - - - - - - - - - - - - - - - - -
fn get_word_count_from_file(file_name: &str) -> Result<u32, &str> {
// if the file is not found on the system, return error
return Err("File can not be found!")
// else, count and return the word count
// let mut word_count: u32; ....
Ok(word_count)
}
// 💭 On the above function,
// instead panic(break) the app, when the file can not be found; return Err(something)
// or when it could get the relevant data; return Ok(data)
// - - - - - - - - - - - - - - - - - - - - - - -
// We can use pattern matching to catch the relevant return type(Ok/Err) when calling it
fn main() {
let mut file_name = "file_a";
match get_word_count_from_file(file_name) {
Ok(i) => println!("Word Count: {}", i),
Err(e) => println!("Error: {}", e)
}
}
🔎 Many useful methods have been implemented around Option and Result types. More information can be found on std::option::Option and std::result::Result pages on Rust doc.
⭐️ Also more practical examples of options & results can be found on Error Handling section in Rust doc.