Rust Lang

Rust Generics

Hello Rustaceans. For this one, we will explore what are generics and how to use generic types in the Rust language. Generics trips most new Rust beginners but we will do our best effort to make it as clear as possible.

If you are interested in exploring more Rust topics, check our Rust list to learn more.

The <T> Parameter

When declaring generic types, you will find the <T> parameter. It is a used to represent any data type in Rust:

An example of the type parameter in a function is a shown:

fn my_function<T>()

The above defines a generic function that takes an argument T of any type.

You can define multiple generic parameters by separating them with commas:

fn my_function<T, U, V>()

In the above syntax, we define multiple generic parameters, T, U, V. Although not required, it is common to find generic types starting from T and incrementing up the alphabet.

Rust Define Generic Struct

We can define a generic struct as shown in the syntax below:

struct StructName<T, U, V, ...> {

// struct info

}

Take the example, struct defined below:

struct MyStruct<T, U> {

i: T,

j: U,

}

fn main() {

let var1 = MyStruct{

i: 10,

j: "Hi"

};

println!("X: {}", var1.i);

println!("Y: {}", var1.j);

}

The above creates a generic struct with two parameters. Inside the struct, we specify two struct members of type T and U. Since T and U are generic types, they can be of any type.

Generic Functions

The same construct can be applied to functions and method. If a function takes type <T>, it is said to be a generic function.

Generic function definition is as shown:

fn func_name<T, U, V, ...>() {

// function logic

}

Take the example of generic function definition below:

fn generic<T>(var: T) {

println!("{}", var);

}

fn main() {

generic("Hi")

}

In this example, we have a generic function that takes any type and prints it to the console. However, running the above code will cause an error.

Since we are using a generic parameter, we need to implement the display or debug trait to print it out. We can refine the code as:

use std::fmt::Debug;

fn generic<T: Debug>(var: T) {

println!("{:?}", var);

}

fn main() {

generic("Hi")

}

In this case, we import the debug trait and use it for the generic type. We can then print the value using the debug trait placeholder: {:?}

The same case applies if a function needs to perform any kind of operation. Take the example function below that returns 0 if a value is even and 1 if otherwise.

fn even_odd<T> (var: T) -> i32 {

if var % 2 == 0 {

return 0;

} else {

return 1;

}

}

If we call the function above, it will return an error. We need to implement the Rem trait for it to work as:

use std::ops::Rem;

use std::cmp::Eq;

fn even_odd<T: Rem<Output = T> + Eq> (var: T) -> i32 {

The Rem and Eq traits allow you to use the remainder operator and perform logical comparison.

Conclusion

In this article, we explored the fundamentals of working with generic types in the Rust language. Check the docs to learn more.

About the author

John Otieno

My name is John and am a fellow geek like you. I am passionate about all things computers from Hardware, Operating systems to Programming. My dream is to share my knowledge with the world and help out fellow geeks. Follow my content by subscribing to LinuxHint mailing list