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.
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 structvar variable1: Int
is a variable that holds an integer valuevar variable2: String
is a variable that holds a string valuefn 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 Mojoobject_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 thestruct
. 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 struct
‘Book
’. 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
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 thestruct
. It’s a way to refer to the current object that you are working on.inout
means that theself
instance of thestruct
is passed to the constructor by reference, not by value. This means that when you make changes inself
inside the function (such as the constructor or method), the changes will be made in the original object outside the function.parameter1
andparameter2
are variables that will be passed to the constructor.Type1
andType2
are the data types of the variables that are being passed to the constructorself.parameter1 = parameter1
assigns the value of the parameter passed by the user (parameter1
) to its data memberparameter1
.self.parameter2 = parameter2
assigns the value of the parameter passed by the user (parameter2
) to its data memberparameter2
.
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.
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.
Same is the case with methods in struct
s. 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
andparameter2
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 ofparameter1
andType2
is the datatype ofparameter2
. 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
andType2
are the data types ofparameter1
andparameter2
respectivelyReturnType
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 ‘Calculato
r’ 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 thestruct
from which you want to use it’s static methodstatic_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.
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.
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.
Syntax
struct StructName: fn __del__(owned self):
StructName
is the name of thestruct
.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 thestruct
.
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.