Rust Generics is a language feature for code reuse in multiple contexts using different actual types for Structs, Functions, Methods, and Enums. Moreover, they allow for concise and clean codes by minimizing boilerplates while providing type-safety. When we use Generics, we are using Generic Data Types in defining, for instance, a Function. Then that Function can work with different actual data types.
This post explains how they work and why they are essential.
How They Generally Work
How we use generics depends on what we are trying to define. Generally, we use any capital letter between the < and > for definition. For example, <U> is a type parameter. U represents any data type.
1 2 3 | fn put_animal_in_crate<U>(animal: U) { // ... } |
We can use multiple type parameters in a single definition.
1 2 3 | fn put_2_mammals_crate<T, U>(mammal1: T, mammal2: U) { // ... } |
Type parameters can have bounds, especially when using traits. Meaning, they are generic enough but must implement those traits. For example, <U:std::fmt::Debug>. U is any type that implements the Debug trait because we need to “debug” its contents.
1 2 3 | fn put_animal_in_crate_and_print<U: std::fmt::Debug>(animal: U) { println!("{:?}", animal); } |
Now, let’s see how we define functions, methods, structs, and enums with Generics.
Rust Generics And Struct Definitions
When using Rust Generics with Struct, place the type parameter right after the Struct name. Then, specify the field’s type using the generic type.
1 2 3 4 5 6 7 | struct BoxOf<A> { content: A } //... let box_of_string: BoxOf<String> = BoxOf { content: "A String".to_string() }; println!("{}", box_of_string.content); |
We can even use multiple type parameters.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | struct BoxOf<A> { content: A } struct BoxOfVarious<A, B, C> { content1: A, content2: B, content3: C, } fn main() { let box_of_various: BoxOfVarious<String, i32, BoxOf<String>> = BoxOfVarious { content1: "Another String".to_string(), content2: 100, content3: BoxOf { content: "A String".to_string() } }; println!("{}", box_of_various.content1); println!("{}", box_of_various.content2); println!("{}", box_of_various.content3.content); } |
Defining Enum with Rust Generics
When using Rust Generics with Enum, place the type parameter right after the Enum name and use the generic types parameters to its values.
1 2 3 4 5 6 7 | #[derive(Debug, PartialEq)] enum MyResult<A, B, C, D> { A(A), B(B), C(C), D(D) } |
Using the Enum is a bit different from using Structs.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | fn main() { let a:MyResult<String, String, String, i32> = MyResult::A("A".to_string()); let b:MyResult<String, String, String, i32> = MyResult::B("B".to_string()); let d:MyResult<String, String, String, i32> = MyResult::D(100); if a == MyResult::A("A".to_string()) { println!("{:?}", a); } if b == MyResult::B("BBBB".to_string()) { println!("{:?}", d); } if d == MyResult::D(100) { println!("{:?}", d); } // Outputs: // A("A") // D(100) } |
Function And Method Definitions With Generic Data Types
Using Generics with Function and Method definitions differ slightly. Functions are independent blocks of codes, whereas methods are functions attached to Struct or its instance. Here is an example of Rust Generics with Struct and its method.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | struct BoxOf<T> { content: T } impl<T> BoxOf<T> { fn get_content(self) -> T { self.content } } fn main() { let box_of_string: BoxOf<String> = BoxOf { content: "A String".to_string() }; let box_of_i32: BoxOf<i32> = BoxOf { content: 10000 }; println!("{}", box_of_string.content); println!("{}", box_of_i32.content); } |
On the other hand, here is an example of Rust Generics with function.
1 2 3 4 5 6 7 8 9 | fn my_function<T>(param: T) -> T { param } fn main() { println!("{}", my_function("A String".to_string())); println!("{}", my_function(200)); println!("{}", my_function(false)); } |
These are only basic examples. There are advanced usages of Rust Generics with Structs, Functions, Methods, and Enums that are beyond the scope of this post.
Tested with Rust 1.41.0. This post is part of the Rust Programming Language For Beginners Tutorial.