When we want to write code for multiple contexts we use Generics. It give us primitives to declare placeholder that allow us to focus less on the specific types. It allows us writing a more concise and clean code by reducing code-duplication and providing type-safety.
Generic Functions
In a generic definition, we write the type parameter between open and close angle brackets after the name. In Rust, generic also describes anything that accepts one or more generic type parameters <T>. T represents any data type.
Syntax :
pub fn function_name<T>(param:T)
T has been specified as a generic type parameter using <T>. It is considered generic when make param of type T. The parameter can be of any type.
Example :
fn main() {
let sum_int_value = addition_of_values(3,5);
println!("Addition of Integer values : {:?}",sum_int_value);
let sum_float_value = addition_of_values(3.1,5.5);
print!("Addition of Float values : {:?}",sum_float_value);
}
pub fn addition_of_values<T: PartialOrd + std::ops::Add<Output = T>>(num1: T, num2: T) -> T {
num1 + num2
}
Output :
Generics Traits
Traits can also be generic. Traits can contain methods or abstract methods. Trait definitions are a way to group method signatures to define a set of behaviours necessary to accomplish some purpose.
Syntax :
trait some_trait {
//abstract method
fn method1(&self);
fn method2(&self){
//contents of method
}
}
Example :
pub trait Summary {
fn summarize_author(&self) -> String;
fn summarize(&self) -> String {
format!("(Read more from {}...)", self.summarize_author())
}
}
pub fn notify<T: Summary>(item: &T) {
println!("Breaking news! {}", item.summarize());
}
The generic type T specified as the type of the item parameters constrains the function.
Generic Struct
To define a generic struct, we write the type parameter after the struct name, between open and close angle brackets. To define a struct, we enter the keyword struct and name the entire struct.
Syntax :
struct STRUCT_NAME<T> {
field:T,
field:T,
}
Then, inside curly brackets, we define the names and types of the pieces of data, which we call fields.
Example :
fn main() {
let int_point = Point {
num1:4,
num2:5
};
println!("Integer points : {:?}",int_point);
let float_point = Point {
num1:4.5,
num2:5.5
};
println!("Floating points : {:?}",float_point);
}
#[derive(Debug)]
struct Point<T> {
num1: T,
num2: T,
}
Output :
Generic Enum
Enums allow you to define a type by enumerating its possible variants. Rust provides us two Enums by default :
- Option<T>
- Result<T, E>
Option<T>
Option enum represents an optional value. Above all, every option has Some and contains a value, or None and does not contain a value.
Syntax :
enum Option<T> {
Some(T),
None,
}
Example :
fn main() {
let result = find_max_value(5,0);
match result{
Some(_) => println!("Num1 is maximum"),
None => println!("Num2 is maximum")
}
}
fn find_max_value<T: PartialOrd + std::ops::Rem<Output = T>>(num1:T,num2:T) -> Option<T> {
match num1 > num2 {
true => Some(num1),
false => None
}
}
Output :
Result<T,E>
Result enum represents either success (Ok) or failure (Err). Sometimes it is important to express why an operation failed. In that case we must use Result enum.
Syntax :
enum Result<T,E> {
Ok(T),
Err(E),
}
- Ok(value): Indicates that the operation succeeded. Value is of type T.
- Err(why): Indicates that the operation failed. Why is of type E.
Example :
fn main() {
let result = find_max_value(5,5);
match result{
Ok(value) => println!("Maximum value is : {}",value),
Err(error) => println!("Error Message : {}",error)
}
}
fn find_max_value(num1:i32,num2:i32) -> Result<i32,String> {
match num1 > num2 {
true => Ok(num1),
false => Err("Equal numbers".to_string())
}
}
Output :
So hopefully you get an idea of how generics are being used in Rust.
Thanks for reading !!
If you want to read more content like this? Subscribe to Rust Times Newsletter and receive insights and latest updates, bi-weekly, straight into your inbox. Subscribe to Rust Times Newsletter: https://bit.ly/2Vdlld7.

