Diving deep into the structs and enums of Rust

Diving deep into the structs and enums of Rust

Rust's structs can become a bit more challenging when we tend to optimize our code, and while fine-tuning the performance of the code, we must use certain enums. Let's dig into the notions of how we can use structures, but not in a naive way.

Let's do a small intro about the structs and enums of rust, and then we shall proceed to the actual code, where we will merge these two in a coincisive way.

As of now, we know that Rust's structs are custom data types that allow us to encapsulate real data fields. On the other hand, enums allow you to define a type by enumerating its possible variants. But the implementations are not quite as straight-forward as they seem.

Forging the problem statement :

We want to create a struct called user with two fields, the user's ID and the user's age, and fetch the values of the specific fields of the struct. If the fields are left empty, then the code should simply print the error message.

Code:

use std::process;
#[derive(Debug)]
struct User {
    id: u32,
    age: u32,
}
impl User {
    fn create_user() -> Result<User, String> {
        let id = 2213;
        let age = 32;
        if id == 0 || age == 0 {
            return Err("Fields cannot be 0".to_string());
        }
        Ok(User { id, age })
    }
}

fn main() {
    let user_inst = User::create_user().unwrap_or_else(|err| {
        println!("Error occured:{}", err);
        process::exit(1);
    });

    let user_id = user_inst.id;
    println!("{}", user_id);
}

Here in the above code snippet, the user struct has two fields: the id field, which has a type of u32, and the age field, which has a type of u32 too. An implementation block, or the Impl block, is defined for our method declaration.

Here inside the impl block, a method named create_user is defined, which mimics the functionalities of a new constructor for creating structs at run time, but unlike a normal new constructor, this create_user is creating the user struct instances at run time but with the help of an enum. As we discussed earlier, merging both enums and structs can be challenging but is good for writing better code.

Explaination of the create_user method

This method is currently creating user instances with two fields, Id and age, at run time. But if we watch closely, we would notice that this method is not just returning a struct User, but rather an enum, or, if I were more specific, a result enum. As we know, a result enum has two variants, Ok and Err. All we want is to create a user instance that has to have two non-empty fields, and if any of the fields are left empty, we will print an error message. And that is done by passing it througn the Err variant, and returning it. If the fields are non-empty, then the struct is going to get created, which is going to get passed through the Ok variant. Thus, by using the result enum, we are not just returning a user struct from the create_user function, but we are returning a result enum.

Note*: The type of our error message is taken as a string.*

Explanation of the main method

fn main() {
    let user_inst = User::create_user().unwrap_or_else(|err| {
        println!("Error occured:{}", err);
        process::exit(1);
    });

    let user_id = user_inst.id;
    println!("{}", user_id);
}

In this main method, a lot of things are happening all at once. At first, inside the user_inst variable, the create_user method is invoked, and it is chained to another method called unwrap_or_else. This method takes a closure as its argument, and this closure takes err as its argument. When the create_user method is called and all the fields are filled, the unwrap_or_else method unwraps the data inside the Ok variant, which would be a struct instance. But when one of the fields of the struct is empty, this closure gets fired, and through its err argument, the actual error message gets printed. So the closure only fires for the invalid entries, which are, in this case, empty entries.

In the next line, to handle the error message more accurately from the process module, the exit() method is called, which takes the argument 1.

This code is not going to end up showing an error message because all the entries are valid, and the code is going to print the user id but for invalid entries, the error message looks like this:


Conclusion :

This was a small demonstration of how we can use Rust's struct and enums in a better way.