Generalising Types and Functionalities using Generics in Rust

Reading Time: 4 minutes

Generics is the way by which we can generalising types and functionalities of the function, structure, etc. Sometimes, we face a problem in which we want to perform the same operation by different types, that time we can implement the same function twice for both types. It can add redundancy to our code. By the use of Generics, we can remove that redundancy.

In this blog, we are going to learn about how we can generalise the types and functionalities in Rust by the use of Generics. Then we will discuss how the Generics impact the code performance.

Generics allows writing a more concise and clean code by reducing code-duplication and providing type-safety. We can make methods, functions, structures, enumerations, collections and traits as generics. In Rust, “Generic” also describes anything that accepts one or more generic type parameters <T>.
The Generic function’s parameter can accept multiple types of data.
The <T> syntax known as the type parameter is used to declare a generic construct. T represents any data-type.

Let’s understand more with the help of an example:

Generic Structure:

// A Generic struct.
struct Box<T>
{
    first:T,
    second:T,
}

fn main()
{
    let integer = Box{ first:2, second:3};
    let float = Box{ first:7.8, second:12.3};
    println!("Sum of integer values : {}", integer.first + integer.second);
    println!("Sum of Float values : {}", float.first + float.second);
}

Output:

This image has an empty alt attribute; its file name is screenshot-from-2019-10-24-23-17-35.png

In this example we have implemented a Generic structure named ‘box‘ having two variables ‘first‘ and ‘second‘ which is of generic type. Then we have used that structure for both integer and float values.

Basically, we have two ways to make our code generic:

  • Option<T>
  • Result<T, E>
Fig : 1: Ways to make code Generic.

Generic Function:

Generics can be used in the functions, and we place the Generics in the signature of the function, where the data type of the parameters and the return value is specified.

Let’s understand more with the help of an example:

// A Generic struct.
#[derive(Debug)]
struct Pair<T> {
    first: T,
    second: T,
}

// Generic function for Swap.
fn swap<T>(pair: Pair<T>) -> Pair<T> {
    let Pair { first, second } = pair;
    Pair { first: second, second: first }
}

// Generic struct implement for two element.
#[derive(Debug)]
struct Tuple<T, U>(T, U);

fn main() {
    let pair_of_chars: Pair<char> = Pair { first: 'a', second: 'b' };
    let pair_of_ints = Pair { first: 1, second: 2 };
    let tuple: Tuple<char, i32> = Tuple('R', 2);

    let swapped_chars = swap::<char>(pair_of_chars);
    let swapped_ints = swap(pair_of_ints);
    
    println!("Swapped pairs of chars: {:?} {:?}", swapped_chars.first, swapped_chars.second);

    println!("Swapped pairs of integers: {:?} {:?}", swapped_ints.first, swapped_ints.second);

    print!("{:?}", tuple);
}

Output:

This image has an empty alt attribute; its file name is screenshot-from-2019-10-25-00-01-58.png

In this example we have implemented a generic function that can swap any type of values. We have taken a character and the integers values. Then we have defined another structure named ‘Tuple‘ which takes two arguments of different types by which we have tried to show you that we can also implement generics for different types.

Impact on code performance of Generics:

You must be thinking that there will be a run-time cost when you’re using generic type parameters. But the Rust can implements generics in such a way that the generics can’t make code slower.

Rust achieve this by executing monomorphization of the code that is using Generics at compile time. Monomorphization is the process of turning generic code into specific code by filling in the concrete types that are used when compiled.

Such as we have used above generic function in our last example for both character and integer, so when the code compiles that function will automatically break into two specific functions for the character and integer type separately.

// Function for chars
fn swap<chars>(pair: Pair<chars>) -> Pair<chars> {
    let Pair { first, second } = pair;

    Pair { first: second, second: first }
}

// Function for integers
fn swap<i32>(pair: Pair<i32>) -> Pair<i32> {
    let Pair { first, second } = pair;
    Pair { first: second, second: first }
}

Because Rust compiles generic code into the code that specifies the type in each instance, we pay no run-time cost for using Generics. When the code runs, it performs just as it would if we had duplicated each definition by hand. The process of monomorphization makes Rust’s generics extremely efficient at run-time.

Thanks for reading!!!

This image has an empty alt attribute; its file name is footer-1.jpg

Written by 

Pankaj Chaudhary is a Software Consultant at Knoldus LLP. He has 1.5+ years of experience with good knowledge of Rust, Python, Java, and C. Now he is working as Rust developer and also works on machine learning and data analysis because he loves to play with data and extract some useful information from it. His hobbies are bike riding and explore new places.

Discover more from Knoldus Blogs

Subscribe now to keep reading and get access to the full archive.

Continue reading