7. Struct
In the previous chapter we saw the different data types supported by Mojo out of the box. But what if you wanted to implement your own data type? Mojo provides struct
keyword for that purpose. The term "struct" was popularized by the ALGOL family of languages and is a short form for the term structure. In Mojo, struct allows one to group related values together as a single unit. Members variables of a struct must have type annotation.
struct Person:
var first_name: String
var last_name: String
fn __init__(inout self):
self.first_name = "Mickey"
self.last_name = "Mouse"
fn get_full_name(self) -> String:
return self.first_name + " " + self.last_name
The code shown before shows how a struct is defined within Mojo. You start with keyword struct
and then give a name for the struct. Then you can define the member variables of the struct. Here we defined first_name
and last_name
strings. You can also define functions within a struct. Functions defined inside the body of a struct are known as "method". The body of the struct is indented with whitespace.
You may have noticed that we have defined a method init
. This is the initializer or in other languages known as the constructor. In order for a struct to be used in a program, we need to define a method that initializes the struct. The main responsibility of the initializer is to setup the struct in a valid state and to ensure that all the member variables also have a valid state. If we omit the initializer, the compiler would complain that init
is missing.
In the init
method, the first argument is mandatory and it is named self
by convention. It does not really matter whether you call the first argument self
or some other name as Mojo would accept any other name. However, Mojo adopts the same convention as Python and calls the first argument self
. It is highly recommended to follow that convention as it makes the code easier to read and understand by other programmers. The first argument also has a keyword inout
. For init
methods, it is mandatory to have inout
keyword in front of the first self
argument. It indicates to Mojo that the self
is mutable reference. We will cover this later on in this book.
To refer to the member variables of a strut, we need to prefix the variable with self.
. Mojo allows new variables to be defined with the same names if the scope of the variable is different. The prefix self.
makes it possible for the Mojo compiler to determine that the struct’s member variable is being referred to and not to another variable of the same name in the function scope.
The anatomy of a struct is shown in the following diagram.
Syntax to instantiate a struct is quite similar to a function call. In the following code listing an instance of Person
is stored in the variable client
.
var client: Person = Person()
print(client.get_full_name())
You can define initializers with additional arguments. You can also define more than one initializers. The initializers can be given arguments similar to how arguments are passed to a function.
struct Person:
var first_name: String
var last_name: String
fn __init__(inout self):
self.first_name = "Mickey"
self.last_name = "Mouse"
fn __init__(inout self, fname: String, lname: String): # Second initializer
self.first_name = fname
self.last_name = lname
fn get_full_name(self) -> String: # Instance method
return self.first_name + " " + self.last_name
fn main():
var client: Person = Person("Donald", "Duck") # Instantiating Person
print(client.get_full_name()) # Calling an instance method
7.1. Instance methods
As mentioned earlier, a struct can define methods within it. There are two types of methods. One is instance method and the other is static method. Instance methods are called on an instance of the struct. In the previous code listing, get_full_name
is an instance method because it the first argument self
which is the instance of the struct. It uses self
to refer to the instance variables of the struct, for example self.first_name
.
To call the instance method, we used the syntax client.get_full_name()
. Note that even though get_full_name
had an argument self
passed to it, we do not pass that argument to get_full_name
when we call it. What is happening here? One way to look at it is that when we call client.get_full_name()
, behind the scene the compiler passes client
as the first argument to get_full_name
. This syntax is quite popular in many object oriented languages, and since Python has this syntax, Mojo also took it over.
7.2. Static methods
What if we do not have to refer to instance variables or even other instance methods in our method, but still want to have the method scoped within the struct? In this case Mojo offers static methods. Static methods are very similar to functions and they are within the scope of the struct, but not bound to a particular instance of the struct. Mojo compiler can perform some optimizations to make static method invocations much faster than instance methods.
struct Vehicle:
var model_name: String
fn __init__(inout self, model_name: String):
self.model_name = model_name
fn get_model(self) -> String:
return self.model_name
@staticmethod
fn get_default_model() -> String:
return "VW"
fn main():
var v: Vehicle = Vehicle("Mercedes")
print(v.get_model()) # Call instance method
print(Vehicle.get_default_model()) # Call static method
print(v.get_default_model()) # Possible, but not a good style to call static method.
print(Vehicle.get_model(v)) # Also possible, but not a good style to call an instance method.
In the previous code listing, get_default_model
was defined using @staticmethod
decorator. We will cover decorators in detail in a later chapter. The @staticmethod
on a method indicates to the Mojo compiler that this method should be a static method.
Static methods are called using the name of the struct itself, instead of the name of the variable that contains the struct’s instance. For example, in the code listing the static method was called by referring to the Vehicle
struct directly as in Vehicle.get_default_model()
.
It is possible to call static methods through an instance of the struct, but that style is discouraged because for a person reading the code, it is confusing.
7.3. Implicit conversion
You may have noticed that assignment var x: String = "A string literal" ` works, even though we saw earlier that anything within the double quotes `""
is of type StringLiteral
. The above assignment works because Mojo has support for implicit conversions.
Mojo has a very simple approach for implicit conversions. Suppose that struct A
has an initializer that takes an argument with type of StringLiteral
. Then when we assign a StringLiteral
to a variable of type A
, it implicitly calls that initializer, resulting in initialization of the variable with an instance of A
with that given string literal passed as an argument.
The following examples makes it more clear.
struct Vehicle:
var model_name: String
fn __init__(inout self, model_name: StringLiteral):
self.model_name = model_name
fn get_model(self) -> String:
return self.model_name
fn main():
var v: Vehicle = "Ford"
print(v.get_model())