Struct in Mojo – A Complete Guide for Beginners

struct in mojo

Learn how to use structs in Mojo to organize and simplify your code. In this article, you’ll explore struct, methods, static methods, constructors, and destructors, and learn how to access them.

What Is a Struct? A Real-world Analogy

In Mojo programming, a struct (short form of structure) stores all the relevant information about something in one place. Think about an ID card that stores all the relevant information of a person such as name, date of birth, ID number, address, and photo. All this information is specific to one person and grouped on a single card.

struct in mojo a real-world analogy

A struct works in a similar way. It stores all the relevant information in one place and makes it easier to organize, manage and access data. It contains methods and variables of different types. Methods are simply the functions that are defined within the struct.

Why Use Struct in Mojo?

Struct is very helpful for reusability. Once you define a struct, you can use it multiple times in the code. Structs also make the code look clean and reduce logical errors by grouping all relevant data together, making it easier to access.

For example, if you want to write code to store data for a person’s ID card, then instead of creating separate variables for each person’s details, you can simply define a structure and create an instance of it for each person. This will keep the code clean, more organized, and easier to maintain.

Defining a Struct in Mojo

To define a struct, first, you need to use the struct keyword followed by the structure’s name. The structure’s name can be anything, however, it’s a good practice to use nouns and follow Pascal Case notation (capitalizing the first letter of each word) when naming it. Then, inside the body, you can define multiple variables and methods, depending on the scope of the struct.

Syntax

struct StructureName:
   var variable1: Int
   var variable2: String
   fn do_nothing(self):
       pass
  • StructureName is the name of the struct
  • var variable1: Int is a variable that holds an integer value
  • var variable2: String is a variable that holds a string value
  • fn do_nothing() is a method of the struct

Example Code

In the following code, we have a struct named ‘Book‘, having three variables. The first variable is of type string that represents the book’s title. The second variable is also of type string and represents the author’s name while the third variable is of type int and stores the total number of pages of the book. The display_info() is a method that prints the details of the book. We will be discussing methods in detail later in this article.

struct Book:
   var title: String
   var author: String
   var pages: Int


   fn __init__(inout self, title: String, author: String, pages: Int):
       self.title = title
       self.author = author
       self.pages = pages


   fn display_info(self):
       print("Title:", self.title)
       print("Author:", self.author)
       print("Pages:", self.pages)

Creating an Object of a Struct in Mojo

Once you define a struct, you can create an object (an instance of the struct). An object represents a single instance of the struct and can be created anywhere outside the struct, wherever you need to use it. To access the object, first, you will need to create a variable and then assign the struct instance to it. Each object represents a separate copy of the struct with its own data.

Syntax

var object_name = StructName(parameter1, parameter2)
  • var is a keyword used to declare a new variable in Mojo
  • object_name is the name of the object. It can be named anything based on its functionality.
  • StructName is the structure’s name from which we want to create an object.
  • (parameter1, parameter2) passes the values of parameter1 and parameter2 to the constructor of the struct. The constructor will then assign these values to the data members (or fields) during its initialization. We will discuss constructors in detail later in this article.

Example Code

Take the same example of the structBook’. If we want to create an object of it, first we will create a variable and then we will assign an instance of the Book to it. During its creation we will be passing two string type values and one int type value to its constructor, in order, so that the data members can be initialized with these values.

 var first_book = Book("James and the Giant Peach","Roald Dahl",160)

What Happens on the Back-end When You Create an Object?

When you create an object, on the backend Mojo allocates memory to that object to hold the variables and methods. If the struct has a constructor, Mojo automatically calls it and initializes all the variables with values passed in the constructor. Once the memory is allocated and initialized, Mojo assigns a reference to it so that you can access the object through a variable. This variable points to the location of the object’s memory and, allows you to access the object by using the variable name.

Consider the example where we create an object first_book and pass the values ‘Dune’, ‘Frank Herbert’, and 200 to its constructor during initialization, the following steps happen:

  • Memory is allocated for the first_book object.
  • The object’s title attribute is set to ‘Dune’.
  • The author attribute is set to ‘Frank Herbert’.
  • The total_pages attribute is set to 200.
  • Finally, a variable called first_book is created to point to the object’s memory location
memory representation of a struct in mojo

Constructor in a Struct

In the previous example, we declared variables inside the struct, but you cannot assign values to these variables directly outside the struct. To assign values to these variables, you need to use a constructor. A constructor is a special type of method that initializes a struct with specific values when you create an instance of that struct.

To declare a constructor in Mojo, define a method called __init__() inside the struct. This method takes parameters for the values you want to assign to the variables declared inside the struct. The first parameter of the constructor is always inout self. The inout self parameter allows the constructor to modify the struct‘s fields.

Syntax

fn __init__(inout self, parameter1: Type1, parameter2: Type2, ...):
   self.parameter1 = parameter1
   self.parameter2 = parameter2
   ...
  • fn __init__() is used to make the constructor.
  • self refers to the instance of the struct. It’s a way to refer to the current object that you are working on.
  • inout means that the self instance of the struct is passed to the constructor by reference, not by value. This means that when you make changes in self inside the function (such as the constructor or method), the changes will be made in the original object outside the function. 
  • parameter1 and parameter2 are variables that will be passed to the constructor.
  • Type1 and Type2 are the data types of the variables that are being passed to the constructor
  • self.parameter1 = parameter1 assigns the value of the parameter passed by the user (parameter1) to its data member parameter1.
  • self.parameter2 = parameter2 assigns the value of the parameter passed by the user (parameter2) to its data member parameter2.

Example Code
Now, if we want to create a constructor for the Book, it will need three parameters, one to initialize the book’s title, the other to initialize the author’s name, and the last one to initialize the total pages of the book. Before all these parameters, you will need to include the input self-parameter in the constructor so that you can modify the structure’s data members.

fn __init__(inout self, title: String, author: String, pages: Int):
       self.title = title
       self.author = author
       self.pages = pages

Real-World Example of a Constructor

Think of it like you’re buying a custom-built PC. Before purchasing, you tell the builder your desired specifications, such as the motherboard, RAM, hard drive, and graphics card so that he can install these things in your PC. The builder then places these components into the PC case. In this scenario, the PC case is like a struct, initially empty, and the builder acts like a constructor, setting up the components inside the case during it’s creation.

real-world example of a constructor in mojo

Methods in a Struct

In Mojo, a method is a function defined within the struct. A method can interact directly with the struct’s data and is used to perform actions related to the struct.

Think about a study lamp. It has all necessary components such as a bulb, wires and electricity but it cannot turn on without the power button. The lamp will only light up when the button is pressed.

real-world analogy of a method in mojo

Same is the case with methods in structs. A struct  can not perform any action without the methods. By using methods, you can perform different functions within a struct. For example, if a struct represents a vehicle, then you can use methods to start the engine, turn on the AC, accelerate, or apply the brakes.

You have to specify the return type of the method. It indicates the type of value which the method will return. You also need to pass a self parameter to a method so that it can access the data members of the struct.

Syntax

struct StructName:
   fn method_name(self, parameter1: Type1, parameter2: Type2) -> ReturnType:
       #method body
  • fn is used to define a function. 
  • method_name is the name of the method. You can name it anything based on the purpose or action of the method.
  • self is passed as a parameter so that the method can access the data members of the object.
  • parameter1 and parameter2 are the parameters that are being passed to the method. The number of these parameters can vary based on the functionality of the method.
  • Type1 is the datatype of parameter1 and Type2 is the datatype of parameter2. This data type can be anything such as integer, float, string or boolean, depending on the required functionality.
  • ReturnType is the type of value the method will return. If a method has no return type, then we can omit this part of method definition.
  • : indicates the start of the method body
  • After that, in the code body you can implement the main logic of the method. 

Example Code

Here, we have a structure named Rectangle. This Rectangle has 2 data members, a constructor and one method named ‘area’. The area method uses self to access the length and width. It calculates the area of the rectangle by multiplying it’s length with its width and then returns it. 

struct Rectangle:
   var length: Int
   var width: Int


   fn __init__(inout self, length: Int, width: Int):
       self.length = length
       self.width = width


   fn area(self) -> Int:
       return self.length * self.width

Static Methods

Static method is a type of method that is tied with the entire struct, rather than with a single instance (object) of the struct. It does not depend on an instance of the struct and can be used without creating an object of the struct. To create a static method, you have to add the ‘@staticmethod’ decorator before the method declaration and don’t include the self-argument in the function.

Syntax

struct MyStruct:
   var variable: Int
   @staticmethod
   fn static_method_name(parameter1: Type1, parameter2: Type2) -> ReturnType:
       # Code for the static method
  • @staticmethod is a decorator which shows that the method is a static method.
  • fn is used to define a function.
  • static_method_name is the name of the static method. You can name it anything, based on the functionality or operations of the method.
  • No self parameter in the method as a static method does not depend on the instance of a struct.
  • parameter1 and parameter 2 are the arguments passed to the static method.
  • Type1 and Type2 are the data types of parameter1 and parameter2 respectively
  • ReturnType is the type of value which the static method will return
  • : indicates the start of the static method’s body.

Example Code

In this code, we have a simple struct named ‘Calculator’ that only contains one method named ‘add’ which adds any two integer variables passed to it and returns it.

struct Calculator:
   @staticmethod
   fn add(variable1: Int, variable2: Int) -> Int:
       return variable1 + variable2

Calling a Static Method

You can directly call the static method by placing a dot ‘.’ operator after the struct name.

Syntax

StructName.static_method_name()
  • StructName is the name of the struct from which you want to use it’s static method
  • static_method_name() is the static method which we want to use

Example Code

Consider the same example of the ‘Calculator’ with a static method ‘add()’. If we want to access the add() method in the main function, we can do it directly by placing a dot ‘.’ operator after the struct name.

fn main():
   print(Calculator.add(5,6))

Output

11

Difference Between Normal and a Static Method

Normal method is associated with an instance of the struct, while a static method is associated with the struct itself. For example, if a struct has one normal method and one static method, and we create three instances of that struct, then each instance will have its own copy of the normal method. However, only one static method will exist for all instances, as it is associated with the struct as a whole, not with individual instances.

static methods vs normal methods

Real World Analogy

Let’s say that you are in a library with many books. If you want to borrow a book, you look for a specific book and then borrow that book. Borrowing a book will only affect the availability status of that one book. In this case, the library (struct) has a normal method like borrow(). This method will only work on the book you choose.

On the other hand if you want to count the total books in the library, then this function does not depend on a single book. It is about all the books present in the library collection. This is where the concept of static methods arises. A static method doesn’t need a specific book. You can call it directly without creating an object, to count all the books. 

difference between normal and static methods of a struct

Destructors in a Struct

Destructors are special types of methods that are used to clean up or release resources when an object goes out of scope and is no longer needed. You do not need to call a destructor once it is defined. They are automatically called with an object goes out of scope or is explicitly destroyed. To define a destructor in Mojo you need to define a method inside the struct called ‘ __del__()’.

Real World Example

Imagine you have a birthday party and decorate your house with balloons and other items. You hire a team to automatically clean up the mess once the party is over. You don’t need to ask them to clean, they will do it on their own. In this case, your house is like a struct, holding all the decorations, while the cleaning team works like a destructor, taking care of everything once the party is done.

destructors of a struct in mojo

Syntax

struct StructName:
   fn __del__(owned self):
  • StructName is the name of the struct.
  • fn is used to define functions.
  • __del__ is the destructor function.
  • owned keyword ensures that the destructor is called when the object goes out of scope or is deleted.
  • self represents an instance of the struct.

Example Code

Now consider the example of ‘Book’. If we want to clear the details of an instance of the Book, once it goes out of scope then we can use a destructor. In the destructor, we would clear the title, author, and total_pages variables of the struct.

struct Book:
   var title: String
   var author: String
   var pages: Int


   fn __init__(inout self, title: String, author: String, pages: Int):
       self.title = title
       self.author = author
       self.pages = pages


   fn display_info(self):
       print("Title:", self.title)
       print("Author:", self.author)
       print("Pages:", self.pages)
         
         
   fn __del__(owned self):
       self.title = ""
       self.author = ""
       self.pages = 0
       print("Book resources cleared")   
         
         
fn main():
   var first_book = Book("James and the Giant Peach","Roald Dahl",160)
   first_book.display_info()

Output

Title: James and the Giant Peach
Author: Roald Dahl
Pages: 160
Book resources cleared

Conclusion

In this article, we learned about structs in Mojo. A struct is like a container that holds related information together, making it easier to organize and manage data. We saw how to define a struct, create objects from it, and use constructors to set values. We also learned how to use methods to perform actions on that data, and how static methods work without needing an object. Finally, we discussed destructors, which help clean up resources when objects are no longer needed. Using structs in Mojo helps keep your code neat and easy to understand, making it simpler to work with different pieces of data in a structured way.

Explore more tutorials on Mojo and its installation guide by Syntax Scenarios to get hands-on practice on this emerging language released by Modular.

Leave a Comment

Your email address will not be published. Required fields are marked *

Scroll to Top