One of the distinguishing features of modern systems programming languages is their ability to construct high-level, safe abstractions atop complex, low-level operations without any performance trade-offs. In Mojo, this is where the struct
type shines. A Mojo struct
is akin to a Python class
, supporting methods, fields, operator overloading, and decorators for
Python classes
are dynamic, allowing dynamic dispatch, structs
, in contrast, is static, binding at compile-time and offering no flexibility for runtime additions or changes. This static nature enhances performance while preserving safety and ease of use.
To illustrate the concept, let’s examine a simple definition of a struct in Mojo:
struct Test:var x: Int ## Define an instance property 'x' of type Int.var y: Int ## Define an instance property 'y' of type Int.fn __init__(inout self, x: Int, y: Int):## Constructor for Test instances.## 'inout self' represents the instance being initialized.## 'x' and 'y' are parameters for initializing the 'x' and 'y' properties.self.x = x ## Assign the 'x' parameter to the 'x' property.self.y = y ## Assign the 'y' parameter to the 'y' property.print("Initialized Test with x =", self.x, ", y =", self.y) ## Print statementfn __lt__(self, rhs: Test) -> Bool:## Less-than comparison method for Test instances.## 'self' is the current instance, and 'rhs' is the instance to compare against.print("Comparing self: (x =", self.x, ", y =", self.y, ") with rhs: (x =", rhs.x, ", y =", rhs.y, ")") ## Print statementreturn self.x < rhs.x or \(self.x == rhs.x and self.y < rhs.y)## Return true if 'x' of the current instance is less than 'x' of 'rhs'.## If 'x' values are equal, compare 'y' values, and return true if 'y' is less.
Line 1: We define a struct
in Mojo called Test
. A struct is a composite data type that can encapsulate multiple values under a single name.
Lines 2–3: We declare two instance properties within the Test
struct. x
and y
are of type Int
. These properties will hold integer values when instances of the Test
struct are created.
Line 5: We define a constructor for the Test
struct. In Mojo, the constructor is named _init_
. It takes three parameters:
inout self
: This parameter represents the instance being initialized and is marked as inout
, indicating that it can be modified within the constructor. The self
parameter is a reference to the current instance of the struct.
x: Int
: This parameter is of type Int
and represents the value that will be assigned to the x
property of the Test
instance.
y: Int
: Similar to x
, this parameter represents the value for the y
property.
Lines 9–10: Within the constructor, we assign the values of the x
and y
parameters to the x
and y
properties of the current Test
instance, respectively. This initializes the instance with the values provided during object creation.
Line 13: We define a method within the Test
struct. The method is named _lt_
and is used for comparing Test
instances. It takes two parameters:
self
: This parameter represents the current instance on which the method is called. It is a reference to the left-hand side of the comparison.
rhs: Test
: This parameter represents the right-hand side of the comparison, another Test
instance, against which the current instance is being compared.
Lines 17–18: This part of the method is the implementation of the less-than comparison. It checks if the x
property of the current instance (self.x
) is less than the x
property of the right-hand side instance (rhs.x
). If this condition is true, it returns true
. If not, it checks whether the x
properties are equal, and if so, it compares the y
properties. If the x
properties are equal and the y
property of the current instance is less than the y
property of the right-hand side instance, it also returns true
. Otherwise, it returns false
.
We can see in the code above that the Mojo struct
members must be explicitly declared using var
or let
declarations. Unlike Python, Mojo's struct structure and contents are predetermined and unalterable during program execution. This means we cannot dynamically modify the attributes of a struct
, such as adding or removing methods, during runtime. However, the static nature of structs brings significant advantages. It contributes to improved program performance, as the program precisely knows where to find the struct’s information and how to utilize it without any additional runtime overhead.
Furthermore, Mojo’s structs seamlessly integrate with features familiar to Python developers, such as operator overloading. This enables us to redefine how common mathematical symbols like +
and -
work with our custom data types. Additionally, it’s worth noting that the “standard types” in Mojo, such as Int
, Bool
, String
, and even Tuple
, are built using structs. This design choice grants us greater flexibility and control over our code, as these types are not hardwired into the language but are accessible as part of the standard toolbox.
Suppose we were using Python instead. This is what our code would have looked like instead:
# Define a Python class named Test.class Test:# Constructor (__init__) for Test instances.def __init__(self, x, y):self.x = x # Initialize the 'x' attribute with the provided 'x' parameter.self.y = y # Initialize the 'y' attribute with the provided 'y' parameter.print(f"Initialized Test with x = {self.x}, y = {self.y}") # Print statement# Less-than comparison method (__lt__) for Test instances.def __lt__(self, rhs):# Print statement before comparison.print(f"Comparing self: (x = {self.x}, y = {self.y}) with rhs: (x = {rhs.x}, y = {rhs.y})")# Check if the 'x' attribute of the current instance is less than 'x' of the right-hand side instance (rhs).if self.x < rhs.x:return True# If 'x' values are equal, compare the 'y' attributes.elif self.x == rhs.x:return self.y < rhs.y# If neither condition is met, return False (greater or equal).return False# Example usagea = Test(3, 4)b = Test(3, 5)# Compare two instancesprint(a < b) # This will trigger the __lt__ method and print the comparison result.
As we can see:
Python classes don’t require explicit type annotations, and the variables are dynamically typed.
Python allows dynamic dispatch, monkey-patching, and runtime changes to classes and methods.
The code is more dynamic and flexible but may come with performance overhead.
While Python offers flexibility and ease of use, it operates differently from Mojo structs, which are more static and performance-oriented. The choice between the two depends on the specific project requirements and trade-offs.
By understanding the differences between these approaches, developers can make informed decisions and choose the right tool for the job, whether it’s Python's dynamic flexibility or Mojo's performance-oriented static nature.
Unlock your potential: Mojo fundamentals series, all in one place!
To deepen your understanding of Mojo, explore our series of Answers below:
What are the fundamentals of Mojo?
Understand the basics of Mojo, its design principles, and how it enhances performance for AI and system-level programming.
What’s the difference between int
and Int
in Mojo?
Learn how Mojo differentiates between built-in primitive types and custom types with enhanced functionality.
What’s the difference between fn
and def
in Mojo?
Discover when to use fn
for performance-oriented functions and def
for flexible, dynamic programming.
What’s the difference between let
and var
in Mojo?
Explore the distinction between let
(immutable) and var
(mutable) variables and their impact on memory safety.
MLIR in Mojo
Understand how Mojo leverages Multi-Level Intermediate Representation (MLIR) for optimizing AI workloads and low-level computations.
What is argument mutability in Mojo?
Learn how Mojo handles mutable and immutable function arguments to enhance performance and safety.
What is a struct in Mojo?
Explore how structs in Mojo provide efficient, memory-safe data structures for performance-critical applications.
Mojo vs. Python
Compare Mojo and Python in terms of speed, memory efficiency, and usability for AI and system-level programming.
Free Resources