Start learning free

LESSON 179 OF 348 · FUNCTIONS & CLASSES

Classes in Rust in Rust

Lesson

Rust is an object oriented programming language. Almost everything in Rust is an object, with its attributes and methods.

Struct creates a user-defined data structure, which holds its own data members (attributes) and member functions (methods), which can be accessed and used by creating an instance of that struct.

Structs are like a blueprint for objects outlining possible behaviors and states that every object of a certain type could have.

Structs are defined via the keyword struct, like this:

// Define an empty struct Employee
struct Employee {
}

Objects/Instances

An Object is an instance of a Struct. A struct is like a blueprint while an instance is a copy of the struct with actual values. It's not an idea anymore, it's an actual employee, like a person named "Andrew" who is 30 years old.

You can have many employees to create many different instances, but without the struct as a guide, you would be lost, not knowing what information is required.

After a struct is defined, we can create objects of the struct. There are two ways of creating objects: locally and on the heap.

Local objects

Local objects behave the same as local variables. They only exist within the scope where they were created. We create a local object just like declaring a variable:

// Define an empty struct Employee
struct Employee {
}

fn main() {
    let employee = Employee {};
}

Heap objects

Heap objects are not bounded by the existance of the scope in which they were created. For example, heap objects created inside a function continue to exist even after the function returns.

A heap object is created using the Box::new() function that returns a smart pointer to the newly constructed object:

// Define an empty struct Employee
struct Employee {
}

fn main() {
    let employee = Box::new(Employee {});
}

Notice the Box::new(). This is because employee is not an Employee value directly, but a smart pointer to the created object. We use this pointer to access and manipulate the actual object, just like we use a remote controller for a TV.

Attributes/Properties

A struct attribute is a Rust field that belongs to a struct and is shared between all the objects of this struct. Let's add some attributes to our Employee struct:

struct Employee {
    pub name: String,
    pub age: i32,
}

fn main() {
    let mut emp1 = Employee { name: String::new(), age: 0 }; // local object

    emp1.name = String::from("Andrew");
    println!("{}", emp1.name); // prints 'Andrew'

    let mut emp2 = Box::new(Employee { name: String::new(), age: 0 }); // heap object

    emp2.age = 40;
    println!("{}", emp2.age); // prints 40
}

We added two properties: the String name and the i32 age. Then, we created two different Employee objects emp1 and emp2.

The attributes of local objects can be accessed and modified using the dot operator (.), like we did above with emp1.name.

The attributes of heap objects can be accessed and modified through the Box pointer using the dot operator (.) as well, since Rust automatically dereferences smart pointers, like we did above with emp2.age.

The keyword pub used before a field name is called an access modifier. name and age are public fields, which means they can be accessed from anywhere in the code, not just inside the struct code.

Constructors

In real life, we want to set the attributes specific to each object when creating it. For example, an employee named Andrew of 30 years old and another one named Mary of 25 years old. Here the constructor comes to rescue.

A constructor is a special type of method (function) which is used to initialize the instance members of the struct.

In Rust the constructor is typically a function named new defined in an impl block and is always called when an object is created.

Let's add a constructor to our Employee struct:

struct Employee {
    pub name: String,
    pub age: i32,
}

impl Employee {
    // constructor:
    fn new(name: String, age: i32) -> Employee {
        Employee { name, age }
    }
}

fn main() {
    let emp1 = Employee::new(String::from("Andrew"), 30); // emp1.name = "Andrew", emp1.age = 30
    println!("{}", emp1.name);

    let emp2 = Box::new(Employee::new(String::from("Mary"), 25)); // emp2.name = "Mary", emp2.age = 25
    println!("{}", emp2.age);
}

The "self"

Every method in Rust has access to its own object through an important reference called self.

The self parameter is explicitly listed in all member functions. Therefore, inside a member function, self may be used to access and manipulate the attributes of that object, like we did above with self.name.

Member functions

Member functions are functions that belong to the struct:

struct Employee {
    pub name: String,
    pub age: i32,
}

impl Employee {
    // constructor:
    fn new(name: String, age: i32) -> Employee {
        Employee { name, age }
    }

    //member functions:
    fn print_name(&self) {
        println!("{}", self.name); // use self to access a field
    }

    fn print_employee(&self) {
        self.print_name();
        println!("{}", self.age);
    }
}

fn main() {
    let emp1 = Employee::new(String::from("Andrew"), 30);

    emp1.print_employee(); // prints "Andrew", 30

    let emp2 = Box::new(Employee::new(String::from("Mary"), 25));

    emp2.print_employee(); // prints "Mary", 25
}

As you can see, we access functions just like we access attributes: using the dot syntax (.) for both local and heap objects in Rust, since Box automatically dereferences.

The function call uses the attributes of the object it was called on. That's why the first print_employee() prints 'Andrew' and 30 and the second prints 'Mary' and 25.

Struct

Struct is the primary way to define custom data types in Rust.

In Rust, struct fields are private by default — you need to add pub to make them accessible from outside the module.

struct Employee {
    name: String,
    age: i32,

    // constructor:
    // (defined in impl block)
}

impl Employee {
    fn new(name: String, age: i32) -> Employee {
        Employee { name, age }
    }

    //member functions:
    fn print_name(&self) {
        println!("{}", self.name);
    }

    fn print_employee(&self) {
        self.print_name();
        println!("{}", self.age);
    }
}

fn main() {
    let emp1 = Employee::new(String::from("Andrew"), 30);

    emp1.print_employee();

    let emp2 = Box::new(Employee::new(String::from("Mary"), 25));

    emp2.print_employee();
}

Assignment
Follow the Coding Tutorial and let's write some classes.


Hint
Look at the examples above if you get stuck.


STEP 1 OF 8
    Ctrl + Enter to check

    Classes in Rust

    Keep going with RustAbout 340 step-by-step lessons, from your first line of code to coding interviews. Free to start.
    Start learning free