Structure - A Custom DataType

Photo by Chor Tsang on Unsplash

Structure - A Custom DataType

This article explains structs in Rust, how to define and instantiate them, and how to implement struct methods.

What are structs?

These are the custom data types that store different types of data together.

Let's create a basic struct to hold student information.

struct Student{
    name: String,
    age: i32,
    email: String,
    semister: String,
    blood_group: String
}

Student struct consists of members which are called fields. Each field will be having a name and associated data type to it.

Instance of Struct

It is possible to create an instance of a struct by stating its name and adding key-value pairs inside curly brackets to define its fields.

Here is an example of creating an instance of the previously defined struct Student.

fn main(){
    let student_1 = Student{
        name: String::from("TheMissingSemicolon"),
        age : 30,
        email: String::from("themissingsemicolon@gmail.com"),
        semister: String::from("4th"),
        blood_group: String::from("AB+ve"),
    };
}

Specifying the fields doesn't need to follow the order in which they were declared in the struct. Struct definitions serve as general templates for types, and instances fill them out with specific data to create values.

Example:

fn main(){
    let student_1 = Student{
        email: String::from("themissingsemicolon@gmail.com"),
        semister: String::from("4th"),
        blood_group: String::from("AB+ve"),
        name: String::from("TheMissingSemicolon"),
        age : 30,
    };
}

Field Init Shorthand

we can write fieldname as a shorthand for fieldname: fieldname when you are initializing a data structure (struct, enum, union). This allows a compact syntax for initialization, with less duplication.

struct Point {
    x: i32,
    y: i32,
}

fn main(){
let x = 10;
let y = 20;
let point1 = Point{x,y}; // we don't need to use Point{x:x,y:y};
}

Struct Instance from another Struct

Creating struct instances sometimes requires us to duplicate code. Here is one example of how we can overcome it.

Suppose an instance of struct Point is present, and you want to create a new instance where only field x needs to be changed. Rest two fields should be of the same value as the old instance. This can be done in a below way.

struct Point {
    x: i32,
    y: i32,
    z: i32,
}

fn main() {
    let x = 10;
    let y = 20;
    let z = 30;
    let p1 = Point { x, y, z };
    let p2 = Point { x: 40, ..point1 };
}

Struct Without Named Fields

We can create structs without named fields that look similar to tuples which are called Tuple Structs.

Tuple structs only contain the types of the fields rather than the names of the fields, but they still have the additional meaning that the struct name provides. Tuple structs are helpful when naming each field because a regular struct would be too verbose or redundant, and you want to give the entire tuple a name and make it a different type from other tuples.

struct Address(String);

fn main() {
    let address = Address(String::from("INDIA"));
}

Struct Without Any Fields

We can create structs without any fields called unit-like structs.

we use the struct keyword, the name we want, and then a semicolon. No need for curly brackets or parentheses! Then we can get an instance of UnitLike in the subject variable in a similar way: using the name we defined, without any curly brackets or parentheses.

struct UnitLike;

fn main() {
    let subject = UnitLike;
}

Accessing Struct Fields

We can access struct fields by using dot (.) notation.

struct Address(String);

fn main() {
    let address = Address(String::from("INDIA"));
    println!("{}", address.0)
    }
}
struct Student {
    name: String,
    age: i32,
    email: String,
    semister: String,
    blood_group: String,
}

fn main() {
    let student_1 = Student {
        name: String::from("TheMissingSemicolon"),
        age: 30,
        email: String::from("themissingsemicolon@gmail.com"),
        semister: String::from("4th"),
        blood_group: String::from("AB+ve"),
    };
    println!("{}", student_1.name);
}

Methods

Methods are similar to functions but are defined in the context of the struct.

Methods can be defined by using the keyword fn following a method name. It can have parameters and return values like functions. But the method's first parameter is always self , which represents the instance of the struct the method is being called on.

To define a method for the struct we use the keyword impl . Everything within this impl block (implementation block) will be associated with the Student type.

#[derive(Debug, Clone)]
struct Student {
    name: String,
    age: i32,
    sid: String,
    blood_group: String,
}

impl Student {
    fn new(name: String, age: i32, sid: String, blood_group: String) -> Self {
        Student {
            name,
            age,
            sid,
            blood_group,
        }
    }

    fn name(&self) -> &str {
        &self.name
    }

    fn sid(&self) -> &str {
        &self.sid
    }
}

fn main() {
    let s = Student::new(
        "ALICE".to_string(),
        32,
        "UNICAL12345".to_string(),
        "A+ve".to_string(),
    );

    println!("Student Name: {:?} and Student Id: {:?}", s.name(), s.sid());
}

This impl block contains several functions and methods which are associated with the struct Student , new acts as a function which need not be called with an instance, name and sid are methods with take the first parameter as self and can only be called on the instance of the struct.

Did you find this article valuable?

Support The Missing Semicolon by becoming a sponsor. Any amount is appreciated!