Understanding Generics in Rust lang

Understanding Generics in Rust lang

ยท

4 min read

Generics is a fundamental part of the rust programming language and can sometimes be a pain to truly understand its use and purpose. In this article, I'll try and explain this concept like you are a kid ( if you are not ).

How to identify a generic

Generics can be associated with functions, enums, structs, return type and variables.

In Function Definitions

pub fn returnData (name: &char) -> &char{
  name
}
pub fn returnData<T> (name: T) -> T{
  name
}

The first function can be identified as a regular function with no generic syntax, but on the other hand, we have another function, which does the same thing but this time uses a generic. We can consider the second function as a better approach, why? Utilizing generics, in this case, gives us the freedom to pass any data type as a parameter, unlike the first function which requires only a parameter of data type &char.

returnData<T> means, this function accepts one parameter of type T. T literally means, any type e.g int, uint, floats, struct, etc. You could actually name is anything but the convention is T which means Type, we can decide to re-write the function using a new name like returnData<MyType>, and this works fine so far as you use this same name as the parameter type (name: MyType) and maybe a return type ( -> MyType). The whole <T> thing you see is the presence and usage of a generic.

fn main() {

pub fn returnData<T> (name: T) -> T{
  name
}
let data = returnData(String::from("Alfred"));

let age = returnData(27);
println!("{} is my name", data);
println!("{} is my age", age);

}

Try it here.

In Struct Definitions

struct User {
  name: &String,
  email: &String,
  age: u8
}
struct User<T,U> {
    name: T,
    email: T,
    age: U
}

Structs are used to create user-defined data structures, and we can use generics to make them even better. You can see the difference between the two structs, the second introduced the generic data type.

Focusing on the second struct, with name as User, we expect this struct to have fields with either a data type of T or/and U. Again, T could be of any type i.e signed integer, unsigned integer, floating-point numbers, or even char.

fn main(){

struct User<T,U,V> {
    name: T,
    email: U,
    age: V
}

struct Fullname {
 fname: String,
 lname: String
}

let user1: User<&str, &str, f64> = User { name: "Alfred", 
email: "alfred@codemon.me", age:10.1};




let _fullname = Fullname { fname: String::from("alfred"), 
lname: String::from("johnson") };

let user2: User<Fullname, &str, u8> = User { name: _fullname, 
email: "fred@gmail.com", age: 32};


println!("User 1: name is {}, email is {} and age is {}",
user1.name, user1.email, user1.age);

println!("User 2: first name is {}, last name is {}, email is {} and age is {}",
user2.name.fname, user2.name.lname, user2.email, user2.age)

}

Try it here.

In enums

Enums are similar to structs mostly in syntax but personally, it's used to define custom data types just like structs. The fields in an enum are expected to be related in some way, unlike struct which gives more flexibility.

enum Colors {
  red,
  green,
  blue,
  custom(String)
}
enum Colors <T>{
  red,
  green,
  blue,
  custom(T)
}

Web developers know we can represent colors in different ways, hex, RGB, or by their name. The first enum does not give room for us to create a custom color, it must be a string. But with the second enum, we can define a custom color in our enum. Assuming we want to represent a custom RGB color, we can proffer a solution as seen below.



fn main(){

enum Colors <T>{
  Green,
  Blue,
  Custom(T)
}

let white_tuple = ("#","f","f","f");
let white: Colors<(&str, &str, &str, &str)> = Colors::Custom(white_tuple);
let blue: Colors<(&str, &str, &str, &str)> = Colors::Blue;
let green: Colors<(&str, &str, &str, &str)> = Colors::Green;
}

Conclusion

I hope you've been able to understand the generic syntax and maybe give you a solution to a problem you have been trying to solve.