Advanced Rust Topics: Dive Deeper into the Language's Capabilities

Rust Generics In Rust Program

Generics accommodates us to write code that is flexible and can be reused with several types of data, except having to write separate implementations for every type. It helps us to write code that can handle values any type in a type-safe and proficient way.

Using Generics in Rust Program

We can understand proficient by taking a look at Rust Hashmap.

HashMap uses generic which allows creation of reusable and proficient code, as a single implementation that works with several types.

A Rust Program HashMap has two generic types, one for the key and the other for the value.

A HashMap type looks as like this:

HashMap<K, V>

When <K, V>: K is the type of the key and V is the type of the value.

Now, when you make a HashMap you can set any type to K and V.

let mut numbers: HashMap<i32, &str> = HashMap::new();

Below, the point of view bracket <i32, &str> notation consoles the type of key and type of value of the HashMap. The type of the key K is i32 also the type of the value V is &str.

Likewise, we make a HashMap and the set of type of both key and value to &str.

let mut language_codes: HashMap<&str, &str> = HashMap::new();

By Using generics to define the type of HashMap helps us work with numerous unrestricted types available in Rust.

Example: Using Generics in Rust Program

use std::collections::HashMap;

fn main() {
    // Create a HashMap with types i32 and &str
    let mut numbers: HashMap<i32, &str> = HashMap::new();

    // Insert values to numbers HashMap
    numbers.insert(1, "One");
    numbers.insert(2, "Two");

    println!("Numbers: {:?}", numbers);
    
    // Create a HashMap with types &str and &str   
    let mut language_codes: HashMap<&str, &str> = HashMap::new();

    // Insert values to language_codes HashMap
    language_codes.insert("EN", "English");
    language_codes.insert("NE", "Nepali");
    
    println!("Language Codes: {:?}", language_codes);
}

Output :

Numbers: {1: "One", 2: "Two"}
Language Codes: {"EN": "English", "NE": "Nepali"}

Below, we make two HashMap data structures: HashMap<i32, &str> and HashMap<&str, &str>.

Generic Struct in Rust Program

We can make a generic struct data structure in Rust Program with the help of generics. For example- we can make declaration a struct with generic parameter(s).

struct Point<T> {
    x: T,
    y: T,
}

Here, we make a struct Point with generic type parameter T in point of view brackets. Under the body of the struct, we can use the T data type for x and y.

So, to use the generic struct Point we can start it and bind it to a variable.

let int_point = Point { x: 1, y: 2 };
let float_point = Point { x: 1.1, y: 2.2 };

We start the Point struct twice, At first with integer values and second with float values.

Example: Generic Struct in Rust Program

fn main() {
    // defining a struct with generic data type
    #[derive(Debug)]
    struct Point<T> {
        x: T,
        y: T,
    }
    
    // initializing a generic struct with i32 data type
    let int_point = Point { x: 1, y: 2 };
    
    // initializing a generic struct with f32 data type
    let float_point = Point { x: 1.1, y: 2.2 };
    
    println!("int_point: {:?}", int_point);
    println!("float_point: {:?}", float_point);
}

Output :

int_point: Point { x: 1, y: 2 }
float_point: Point { x: 1.1, y: 2.2 }

Generic Function in Rust Program

We can also make functions with generic types as parameter(s).

Now here is the syntax of a generic function-

// generic function with single generic type
fn my_function<T>(x: T, y: T) -> T {
    // function body
    // do something with `x` and `y`
}

// generic function with multiple generic types
fn my_function<T, U>(x: T, y: U) {
    // function body
    // do something with `x` and `y`
}

Below, <T> in the function definition notes a generic function over type T. Likewise, <T, U> notes a generic function over type T and U.

Example: Generic Function in Rust Program

fn main() {
    // generic function to find minimum between two inputs
    fn min<T: PartialOrd>(a: T, b: T) -> T {
        if a < b {
            return a;
        } else {
            return b;
        }
    }

    // call generic function with integer type as parameters    
    let result1 = min(2, 7);

    // call generic function with float type as parameters
    let result2 = min(2.1, 1.1);
    
    println!("Result1 = {}", result1);
    println!("Result2 = {}", result2);
}

Output :

Result1 = 2
Result2 = 1.1

In this example, we make a function min() with generic type contentions a: T and b: T. The type parameter T is published with the syntax <T: PartialOrd>, what means that T can be any type that appointments the PartialOrd trait.

Pattern Matching In Rust

Pattern matching is a avenue to match the structure of a value and bind variables to its parts. That is a powerful avenue to handle data and control flow of a Rust programing.

We usually use the match evolutions when it comes to pattern matching.

The syntax of the match evolutions is:

match VALUE {
    PATTERN => EXPRESSION,
    PATTERN => EXPRESSION,
    PATTERN => EXPRESSION,
}

Hera, PATTERN => EXPRESSION are called patterns, a special syntax in Rust which usually works together with the match keyword.

Previous
Rust Modules: A Comprehensive Guide to Packages and Best Practices