Skip to content

Convert Python code into Mojo

In this chapter, we will look at some simple examples of Python and Mojo code. It covers the basic syntax, data types, functions, structs, control flows, error handling, and some common idioms in both languages. The goal is to help you have a image of how Mojo looks like and how it is similar to or different from Python.

Case 1: Multiplication table

The first example is about multiplication table (Wiki page). The multiplication table is a table of numbers that shows the result of multiplying two integral numbers together. When I am kid, before going to the elementary school, I was already able to memorize the Chinese multiplication table (jǐujǐuchéngbiǎo / Nine-nine song). It is a powerful tool for learning multiplication and finding the product of two numbers quickly.

So the first example is to print a multiplication table from 1 to 9. Each element would be of the form i * j = k. All the elements in the same row are separated by a tab character. Since the multiplication is symmetric, we do not need to repeat the calculation. For example, we skip 3 * 1 = 1 and 3 * 2 = 6 but continue with 3 * 3 = 9.

We do this first in Python. We create a file in the src/move/ directory called multiplication_table.py and write the following code in it:

python
# src/move/multiplication_table.py
def main():
    print("Nine-nine Multiplication Table")
    for i in range(1, 10):
        for j in range(i, 10):
            print("{} * {} = {}".format(i, j, i*j), end="\t")
        print()

main()
text
Nine-nine Multiplication Table
1 * 1 = 1       1 * 2 = 2       1 * 3 = 3       1 * 4 = 4       1 * 5 = 5       1 * 6 = 6       1 * 7 = 7       1 * 8 = 8       1 * 9 = 9
2 * 2 = 4       2 * 3 = 6       2 * 4 = 8       2 * 5 = 10      2 * 6 = 12      2 * 7 = 14      2 * 8 = 16      2 * 9 = 18
3 * 3 = 9       3 * 4 = 12      3 * 5 = 15      3 * 6 = 18      3 * 7 = 21      3 * 8 = 24      3 * 9 = 27
4 * 4 = 16      4 * 5 = 20      4 * 6 = 24      4 * 7 = 28      4 * 8 = 32      4 * 9 = 36
5 * 5 = 25      5 * 6 = 30      5 * 7 = 35      5 * 8 = 40      5 * 9 = 45
6 * 6 = 36      6 * 7 = 42      6 * 8 = 48      6 * 9 = 54
7 * 7 = 49      7 * 8 = 56      7 * 9 = 63
8 * 8 = 64      8 * 9 = 72
9 * 9 = 81

On your own machine, run it with python src/move/multiplication_table.py.


Now we program in Mojo. A clever way is to simply copy the above Python file and change the file extension to .mojo. Then we remove the last line main() because it is not needed.

Let's compile and run this Mojo code by pixi run mojo src/move/multiplication_table.mojo. You will see the following output:

console
Nine-nine Multiplication Table
1 * 1 = 1       1 * 2 = 2       1 * 3 = 3       1 * 4 = 4    1 * 5 = 5    1 * 6 = 6       1 * 7 = 7       1 * 8 = 8    1 * 9 = 9
2 * 2 = 4       2 * 3 = 6       2 * 4 = 8       2 * 5 = 10   2 * 6 = 12    2 * 7 = 14      2 * 8 = 16      2 * 9 = 18
3 * 3 = 9       3 * 4 = 12      3 * 5 = 15      3 * 6 = 18   3 * 7 = 21    3 * 8 = 24      3 * 9 = 27
4 * 4 = 16      4 * 5 = 20      4 * 6 = 24      4 * 7 = 28   4 * 8 = 32    4 * 9 = 36
5 * 5 = 25      5 * 6 = 30      5 * 7 = 35      5 * 8 = 40   5 * 9 = 45
6 * 6 = 36      6 * 7 = 42      6 * 8 = 48      6 * 9 = 54
7 * 7 = 49      7 * 8 = 56      7 * 9 = 63
8 * 8 = 64      8 * 9 = 72
9 * 9 = 81

Wow! The program runs successfully with the same output as in Python!

Older versions of Mojo

In older versions of Mojo (before v25.5), a StringLiteral object will not be materialized to a String object automatically at run time. When you call a method of String type (in this case, format()) on a StringLiteral object, you will get an error.

So you have to wrap the string literal with String() constructor to explicitly convert it to a String object before calling the method.

For your comparison, I put the complete Mojo code as well as the Python code below:

mojo
# src/move/multiplication_table.mojo
def main():
    print("Nine-nine Multiplication Table")
    for i in range(1, 10):
        for j in range(i, 10):
            print("{} * {} = {}".format(i, j, i*j), end="\t")
        print()
python
# src/move/multiplication_table.py
def main():
    print("Nine-nine Multiplication Table")
    for i in range(1, 10):
        for j in range(i, 10):
            print("{} * {} = {}".format(i, j, i*j), end="\t")
        print()

main()

Great! We see that we can migrate our Python code to Mojo so easily, while enjoying the performance of Mojo. How big is the performance gain? Let's check it out using the next example.

Difference between Python and Mojo

The table below summarizes the differences between Python and Mojo in this example.

FeaturePythonMojo
main() functionNot neededMandatory as an entry point
String typeString literal is coerced to str typeString literal is materialized to String object
String formattingstr.format() methodString.format() method
f-stringsSupportedNot supported, but there are t-strings
formatted valuesSupported, e.g., {:0.2f}, {:0.3%}Not supported (yet)

Case 2: Fibonacci sequence

To demonstrate the gain in speed, we use Fibonacci sequence as an example. The Fibonacci sequence is a sequence of numbers in which each number is the sum of the two preceding ones, usually starting with 0 and 1.

What we want to do is to calculate the first 40 Fibonacci numbers and print them out. Although there is a more efficient way to implement this, I will use the recursive method so that we can see the performance difference between Python and Mojo.

Let's create a file in the src/move/ directory called fibonacci.py and write the following code in it:

python
# src/move/fibonacci.py
def fib(n: int) -> int:
    if n <= 1:
        return n
    return fib(n - 1) + fib(n - 2)

def main():
    for i in range(40):
        print(fib(i), end=", ")

main()
text
0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377, 610, 987, 1597, 2584, 4181, 6765, 10946, 17711, 28657, 46368, 75025, 121393, 196418, 317811, 514229, 832040, 1346269, 2178309, 3524578, 5702887, 9227465, 14930352, 24157817, 39088169, 63245986,

On your own machine, run it with python src/move/fibonacci.py.

On my machine (Apple M4 Pro), it takes about 24 seconds to run the code. This is because the recursive method is very inefficient. The time complexity is exponential.


Let's migrate the code to Mojo and see how fast it is.

Just like what we did in the previous example, we copy the above Python file and change the file extension to .mojo. Then we remove the last line main().

Now you will see that your IDE is complaining about the first line of the code. It highlights the type hints int and tells you use of unknown declaration 'int'. Running the code will also give your the same error. This is because Mojo's built-in integral type is called Int (with capital "I") which is different from Python. So we need to change the type hints from int to Int. The modified code is as follows:

mojo
# src/move/fibonacci.mojo
def fib(n: Int) -> Int:
    if n <= 1:
        return n
    return fib(n - 1) + fib(n - 2)


def main():
    for i in range(40):
        print(fib(i), end=", ")
python
# src/move/fibonacci.py
def fib(n: int) -> int:
    if n <= 1:
        return n
    return fib(n - 1) + fib(n - 2)

def main():
    for i in range(40):
        print(fib(i), end=", ")

main()

Let's run the code again with pixi run mojo src/move/fibonacci.mojo. On my machine, it takes around 0.35 seconds only.

See, a huge performance gain! The Mojo code is more than 50 times faster than the Python code. What we did is just copying-pasting from the Python code and making small modifications, but we gained a performance comparable to C.

Difference between Python and Mojo

The table below summarizes the differences between Python and Mojo in this example.

FeaturePythonMojo
Integral typeint (big integer)Int (fixed-size integer)

Naming convention of Mojo type

In Python, types and classes are usually named with CamelCase. But there are some exceptions: primitive types like int, float, str, etc, are name with lowercase. For consistency, Mojo uses CamelCase for all types and classes. So int in Python is Int in Mojo, float is Float64, str is String, etc.

Why Mojo is faster than Python?

The main reason is that Mojo is a compiled language which is complied to machine code via LLVM and then executed directly by the CPU. Python, on the other hand, is an interpreted language. It is compiled to bytecode and is then interpreted by the Python virtual machine. This leads to a lot of work at runtime that slows down the execution.

There are some other reasons why Mojo is much faster. In this example. One reason is that the Python's integral type is different from Mojo's integral type. The int type in Python is actually a big integer type which can be arbitrarily large. This means that every int type requests memory allocation and deallocation on heap. In Mojo, Int is a fixed-size integer type (32-bit or 64-bit depending on the system) and it is allocated on stack.

Speed comparison with C, Rust, et al.

Now you know that Mojo is much faster than Python. You may wonder how the performance of Mojo is compared to C, Rust, and other static-typed languages. To answer this question, I will implement the same Fibonacci sequence code in C, Rust, Go, Swift and Zig.

Each program now times itself, so that you can compare the languages by pressing the Run button on each tab instead of taking my word for it. They all calculate the first 40 Fibonacci numbers, which the compiled languages finish in well under a second. Python takes far longer, and the website will stop it before it is done. That is the whole point 😉.

The C, Rust, Go, Swift, Zig, Mojo, and Python codes are as follows:

c
#include <stdint.h>
#include <stdio.h>
#include <time.h>

int64_t fib(int n) {
  if (n <= 1) {
    return n;
  }
  return fib(n - 1) + fib(n - 2);
}

int main() {
  clock_t start = clock();
  for (int i = 0; i < 40; i++) {
    printf("%lld, ", (long long)fib(i));
  }
  printf("\nTime: %f seconds\n", (double)(clock() - start) / CLOCKS_PER_SEC);
}
rust
use std::time::Instant;

fn fib (n: i64) -> i64 {
    if n <= 1 {
        return n;
    }
    return fib(n - 1) + fib(n - 2);
}

fn main() {
    let start = Instant::now();
    for i in 0..40 {
        print!("{}, ", fib(i));
    }
    println!("\nTime: {} seconds", start.elapsed().as_secs_f64());
}
go
package main

import (
    "fmt"
    "time"
)

func fib(n int64) int64 {
    if n <= 1 {
        return n
    }
    return fib(n-1) + fib(n-2)
}

func main() {
    start := time.Now()
    for i := int64(0); i < 40; i++ {
        fmt.Printf("%d, ", fib(i))
    }
    fmt.Printf("\nTime: %v seconds\n", time.Since(start).Seconds())
}
swift
import Foundation

func fib(_ n: Int64) -> Int64 {
    if n <= 1 {
        return n
    }
    return fib(n - 1) + fib(n - 2)
}

func main() {
    let start = Date()
    for i in 0..<40 {
        print("\(fib(Int64(i))), ", terminator: "")
    }
    print("\nTime: \(-start.timeIntervalSinceNow) seconds")
}

main()
zig
const std = @import("std");

fn fib(n: i64) i64 {
    if (n <= 1) {
        return n;
    }
    return fib(n - 1) + fib(n - 2);
}

pub fn main() !void {
    var timer = try std.time.Timer.start();
    var i: i64 = 0;
    while (i < 40) : (i += 1) {
        std.debug.print("{d}, ", .{fib(i)});
    }
    const seconds = @as(f64, @floatFromInt(timer.read())) / 1e9;
    std.debug.print("\nTime: {d} seconds\n", .{seconds});
}
mojo
from std.time import perf_counter_ns


def fib(n: Int64) -> Int64:
    if n <= 1:
        return n
    return fib(n - 1) + fib(n - 2)


def main():
    var start = perf_counter_ns()
    for i in range(40):
        print(fib(Int64(i)), end=", ")
    print()
    print("Time:", Float64(perf_counter_ns() - start) / 1e9, "seconds")
python
import time


def fib(n: int) -> int:
    if n <= 1:
        return n
    return fib(n - 1) + fib(n - 2)


def main():
    start = time.perf_counter()
    for i in range(40):
        print(fib(i), end=", ")
    print()
    print("Time:", time.perf_counter() - start, "seconds")

main()

They all print the same forty numbers:

console
0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377, 610, 987, 1597, 2584, 4181, 6765, 10946, 17711, 28657, 46368, 75025, 121393, 196418, 317811, 514229, 832040, 1346269, 2178309, 3524578, 5702887, 9227465, 14930352, 24157817, 39088169, 63245986,

But not in the same time. I ran all seven on my own computer, a MacBook Pro with an Apple M4 Pro chip, with the compilers set to optimize. Each number below is the average of a hundred runs. Time is in seconds:

LanguageRunsMeanMedianFastestSlowestStandard deviation
Mojo1000.2580.2580.2520.2680.003
C1000.2620.2610.2540.2770.004
Rust1000.2680.2680.2580.2850.006
Zig1000.2690.2680.2640.2810.003
Swift1000.3420.3410.3280.3650.009
Go1000.3560.3540.3470.3760.006
Python10012.48612.45012.09815.5260.346

The top four are within four hundredths of a second of one another. Do not read too much into their order: they all compile the same recursion down to much the same machine code, and on your computer they may well line up differently. Swift and Go take about a third longer. Python needs forty-eight times as long as any of them. That gap is the comparison this section is about.

To be frank, I am quite satisfied with the performance of Mojo in this case. It keeps company with C, Rust, and Zig, and it is a far younger language than any of them.


Earlier, I ran the same test with 50 Fibonacci numbers, which is about a hundred and twenty times more work than 40, so the numbers are much larger. Python needed almost an hour, which is why the Miji now uses 40.

LanguageTime (seconds)CommandSize of executable
C31gcc -O3 fibonacci.c33 KB
Zig32zig build-exe -O ReleaseFast fibonacci.zig51 KB
Mojo32mojo build fibonacci.mojo39 KB
Rust34rustc -O fibonacci.rs450 KB
Swift42swiftc -O fibonacci.swift51 KB
Go43go build -ldflags="-s -w" fibonacci.go1.4 MB
Python3440--

Note that the executable sizes differ a lot more than the times do.

Case 3: Sort numbers

In the next example, we will look into more more complex data structure: lists. The goal is to show you more about the differences between Python and Mojo. You have to modify your Python code a bit more than the previous examples so that it can be run in Mojo.

This example is to sort an array of numbers in ascending order in-place. There are many sorting algorithms. We will use the bubble sort algorithm because it is relatively simple and won't take too many lines of code.

The bubble sort algorithm repeatedly scan through the array, compares each pair of adjacent elements and swaps them if they are in the wrong order. For example, the array 5, 2, 9, 1 will be sequentially sorted to 2, 5, 1, 9 -> 2, 1, 5, 9 -> 1, 2, 5, 9 after iterations.

Let's do this first in Python. We create a file in the src/move directory called sort.py and write the following code in it:

python
# src/move/sort.py
def bubble_sort(array):
    n = len(array)
    for i in range(n):
        for j in range(0, n - 1 - i):
            if array[j] > array[j + 1]:
                array[j], array[j + 1] = array[j + 1], array[j]


def main():
    array = [64.1, 34.523, 25.1, -12.3, 22.0, -11.5, 90.49]
    print("Input array:", array)
    bubble_sort(array)
    print("After sorting:", array)


main()
text
Input array: [64.1, 34.523, 25.1, -12.3, 22.0, -11.5, 90.49]
After sorting: [-12.3, -11.5, 22.0, 25.1, 34.523, 64.1, 90.49]

On your own machine, run it with python src/move/sort.py.

We can visually verify that the array is correctly sorted in ascending order.


Now we try to migrate the code to Mojo. Just like what we did in the previous examples, we copy the above Python file and change the file extension to .mojo. Then we remove the last line main().

Now you immediately see that you IDE is complaining more errors than before. The first error message is:

console
error: argument type must be specified
def bubble_sort(array):
                ^~~~~

This is because Mojo is a statically typed language. You have to explicitly specify the type of the argument and the type of the return value, so that the compiler can allocate the correct amount of memory on stack for the function call.

On contrary, Python is a dynamically typed language. You do not need to specify the type of the argument and the return value. The Python interpreter will infer the type at runtime. If the type is not subscriptable (i.e., you cannot use [] to access the element), it will raise an error. You can also add type hints in Python, which is a good practice for static checks, readability and maintainability, but it is not mandatory.

The type hint in Python is something like:

python
def bubble_sort(array: list[float]):
    ...

In Mojo, as mentioned above, the types may have different naming conventions. Beside using Camel Case (e.g., list -> List), the floating-point type in Mojo is named as Float64 instead of float.

mojo
def bubble_sort(array: List[Float64]):
    ...

After the change, you will see that the IDE is still complaining:

console
error: expression must be mutable in assignment
                array[j], array[j + 1] = array[j + 1], array[j]
                     ~~~^~~~~~~~

This is another important and new feature of Mojo: the arguments cannot be modified at will within the body of a function. This is to avoid unintentional changes to the original variable that is passed into the function. If you intend to change the value of the argument within the function, you have to explicitly declare the argument as mutable. This is done via several special keywords in the function signature. We will discuss this more in Chapter Functions. For now, we just add the keyword mut in front of the argument array. By doing this, we tell Mojo that we want to modify the variable array by using this function.

mojo
def bubble_sort(mut array: List[Float64]):
    ...

Now you will see that the errors on the function bubble_sort() are gone. There is one more thing to fix, though. In Mojo, a bare list literal does not give you a List; it gives you an Array, which has a fixed length. Our bubble_sort() asks for a List[Float64], so we say so when we create the array:

mojo
def main():
    var array: List[Float64] = [64.1, 34.523, 25.1, -12.3, 22.0, -11.5, 90.49]
    ...

We will come back to the difference between Array and List in Chapter Composite data types.

After all changes, we have our final Mojo code as follows:

mojo
# src/move/sort.mojo

def bubble_sort(mut array: List[Float64]):
    var n = len(array)
    for i in range(n):
        for j in range(0, n - 1 - i):
            if array[j] > array[j + 1]:
                array[j], array[j + 1] = array[j + 1], array[j]


def main():
    var array: List[Float64] = [64.1, 34.523, 25.1, -12.3, 22.0, -11.5, 90.49]
    print("Input array:", array)
    bubble_sort(array)
    print("After sorting:", array)
python
# src/move/sort.py
def bubble_sort(array):
    n = len(array)
    for i in range(n):
        for j in range(0, n - 1 - i):
            if array[j] > array[j + 1]:
                array[j], array[j + 1] = array[j + 1], array[j]


def main():
    array = [64.1, 34.523, 25.1, -12.3, 22.0, -11.5, 90.49]
    print("Input array:", array)
    bubble_sort(array)
    print("After sorting:", array)


main()
text
Input array: [64.1, 34.523, 25.1, -12.3, 22.0, -11.5, 90.49]
After sorting: [-12.3, -11.5, 22.0, 25.1, 34.523, 64.1, 90.49]
text
Input array: [64.1, 34.523, 25.1, -12.3, 22.0, -11.5, 90.49]
After sorting: [-12.3, -11.5, 22.0, 25.1, 34.523, 64.1, 90.49]

Running the code with pixi run mojo src/move/sort.mojo gives you exactly the same output as Python, down to the last bracket.

Printing a list used to need a helper function

If you followed an older version of this Miji, this example ended rather differently. Until Mojo v1.0.0, print() on a list of floats printed the type of every element:

console
Input array: [SIMD[DType.float64, 1](64.1), SIMD[DType.float64, 1](34.523), ...]

which was informative and unreadable in equal measure. We had to write a helper function to print a list in Python's style:

mojo
def print_list(array: List[Float64]):
    print("[", end="")
    for i in range(len(array)):
        if i < len(array) - 1:
            print(array[i], end=", ")
        else:
            print(array[i], end="]\n")

You no longer need it. If you meet such a helper in older Mojo code, this is what it was for.

Successful!

For this example, we have to change more lines to adapt our Python code to Mojo, including:

  • Explicitly specify the type of the arguments of the function.
  • Use the keyword mut before arguments in function signature if you want to modify their values.
  • Annotate the variable as a List when creating it from a list literal.

If you already use the type-hint system a lot in Python, you will not find these changes too difficult. If you do not use the type-hint system in Python, you may find it a bit annoying and frustrated. But believe me, it is a good practice to declare the types of variables and arguments of functions, in both Mojo and Python. It makes our code more readable and maintainable, and it enables the linter to do static checks and find out potential bugs before you run the code.

Difference between Python and Mojo

The table below summarizes the differences between Python and Mojo in this example.

FeaturePythonMojo
Integral typeint (big integer)Int (fixed-size integer)
Type annotation in function signatureOptionalMandatory
Annotation for the list typea: list[float]var a: List[Float64]
Type annotation when creating listsOptionalNeeded, otherwise a literal gives you an Array
Types of elements in a listHeterogeneousHomogeneous
Mutable argumentDefaultMust use mut keyword
Print list with print()SupportedSupported, and prints the same as Python

Case 4: Triangle type

In the last example, we will look into a more complex data structure of Python: "class". Class is a container for variables (attributes) and functions (methods), so that these variables and functions can be accessed via a uniformed interface. It also allows us to achieve the object-oriented programming (OOP )paradigm, which is a powerful way to organize our code and data.

In this example, we will define a class to represent a triangle. The Triangle class (1) reads in three sides from the user, (2) saves these sides as attributes, and (3) do some verification to check if the three sides can form a valid triangle. The Triangle class also contains two methods: one to calculate the perimeter of the triangle and another to calculate the area of the triangle using Heron's formula:

S=s(sa)(sb)(sc)

Now let's do this in Python first. We create a file in the src/move/ directory called triangle.py and write the code in it.

As a Pythonista, you may want to do this yourself first since it is not difficult. If you are not sure how to do this, you can refer to the code below.

Note that I will use a more stricter style of Python code, which means that I will include "docstrings" for the class and the methods. I will also use type annotations for variables and arguments if necessary.

python
# src/move/triangle.py
class Triangle:
    """A class to represent a triangle."""

    def __init__(self, a: float, b: float, c: float):
        """Initializes a triangle with three sides.

        Parameters:
            a (float): Length of side a.
            b (float): Length of side b.
            c (float): Length of side c.

        Raises:
            ValueError: If the lengths do not form a valid triangle.
        """
        self.a = a
        self.b = b
        self.c = c

        if (
            (self.a + self.b <= self.c)
            or (self.a + self.c <= self.b)
            or (self.b + self.c <= self.a)
        ):
            raise ValueError("The lengths of sides do not form a valid triangle.")

    def area(self) -> float:
        """Calculates the area of the triangle using Heron's formula.

        Returns:
            float: The area of the triangle.
        """
        s = (self.a + self.b + self.c) / 2
        return (s * (s - self.a) * (s - self.b) * (s - self.c)) ** 0.5

    def perimeter(self) -> float:
        """Calculates the perimeter of the triangle.

        Returns:
            float: The perimeter of the triangle.
        """
        return self.a + self.b + self.c

    def __str__(self) -> str:
        """Returns a string representation of the triangle.

        Returns:
            A string representation of the triangle.

        Notes:
            You can use the `str()` or `print()` to call this method.
        """
        return f"Triangle(a={self.a}, b={self.b}, c={self.c})"


def main():
    # A valid triangle with sides 3, 4, and 5
    print("Creating a valid triangle with sides 3, 4, and 5:")
    triangle = Triangle(3, 4, 5)
    print(triangle)
    print(f"Area: {triangle.area()}")
    print(f"Perimeter: {triangle.perimeter()}")

    # An invalid triangle with sides 1, 2, and 3
    print("\nCreating an invalid triangle with sides 1, 2, and 3:")
    try:
        invalid_triangle = Triangle(1, 2, 3)
        print(invalid_triangle)
    except ValueError as e:
        print(f"Error: {e}")


main()
text
Creating a valid triangle with sides 3, 4, and 5:
Triangle(a=3, b=4, c=5)
Area: 6.0
Perimeter: 12

Creating an invalid triangle with sides 1, 2, and 3:
Error: The lengths of sides do not form a valid triangle.

On your own machine, run it with python src/move/triangle.py.

It is as expected. The first triangle is a valid triangle with sides 3, 4, and 5. The area is 6.0 and the perimeter is 12. The second triangle is not a valid triangle with sides 1, 2, and 3, so it raises a ValueError exception. This exception was successfully caught by the try-except statement and printed out.


Now, let's migrate the code to Mojo. Just like what we did in the previous examples, we copy the above Python file and change the file extension to .mojo.

We first do some simple changes to the code using the knowledge we have learned so far:

  • We change the type hints from float to Float64 and from str to String.
  • We use format() method instead of "f-string".
  • Remove main() at the end of the file.

After these changes, we have the following code:

mojo
# src/move/triangle_from_py.mojo
# Adapted from Python code with preliminary changes
# It won't compile yet

class Triangle:
    """A class to represent a triangle."""

    def __init__(self, a: Float64, b: Float64, c: Float64):
        """Initializes a triangle with three sides.

        Parameters:
            a (Float64): Length of side a.
            b (Float64): Length of side b.
            c (Float64): Length of side c.

        Raises:
            ValueError: If the lengths do not form a valid triangle.
        """
        self.a = a
        self.b = b
        self.c = c

        if (
            (self.a + self.b <= self.c)
            or (self.a + self.c <= self.b)
            or (self.b + self.c <= self.a)
        ):
            raise ValueError(
                "The lengths of sides do not form a valid triangle."
            )

    def area(self) -> Float64:
        """Calculates the area of the triangle using Heron's formula.

        Returns:
            Float64: The area of the triangle.
        """
        s = (self.a + self.b + self.c) / 2
        return (s * (s - self.a) * (s - self.b) * (s - self.c)) ** 0.5

    def perimeter(self) -> Float64:
        """Calculates the perimeter of the triangle.

        Returns:
            Float64: The perimeter of the triangle.
        """
        return self.a + self.b + self.c

    def __str__(self) -> String:
        """Returns a string representation of the triangle.

        Returns:
            A string representation of the triangle.

        Notes:
            You can use the `str()` or `print()` to call this method.
        """
        return "Triangle(a={}, b={}, c={})".format(
            self.a, self.b, self.c
        )


def main():
    # A valid triangle with sides 3, 4, and 5
    print("Creating a valid triangle with sides 3, 4, and 5:")
    triangle = Triangle(3, 4, 5)
    print(triangle)
    print("Area: {}".format(triangle.area()))
    print("Perimeter: {}".format(triangle.perimeter()))

    # An invalid triangle with sides 1, 2, and 3
    print("\nCreating an invalid triangle with sides 1, 2, and 3:")
    try:
        invalid_triangle = Triangle(1, 2, 3)
        print(invalid_triangle)
    except ValueError as e:
        print("Error:", e)

This will be the starting point of our Mojo code. You will see many error messages when you run the code with pixi run mojo src/move/triangle_from_py.mojo. The first error message is, which should also be highlighted by the IDE is about the first line:

console
/Users/ZHU/Programs/my-first-mojo-project/src/move/triangle.mojo:1:1: error: classes are not supported yet
class Triangle:
^

Ah, "classes are not supported yet" in Mojo! This is a bit disappointing to you. You may ask: does this mean that Mojo cannot fulfill the OOP paradigm? The answer is no. Mojo does not support classes yet, but it supports a similar concept called "struct".

Structs are similar to classes, containing variables (fields) and functions (methods). They can achieve encapsulation, data abstraction, polymorphism. The only thing that structs do not support is inheritance. It means that you cannot create a new struct that inherits from an existing struct. It is a different design philosophy called "composition", which is still a way to achieve OOP paradigm. We will discuss more about structs in Chapter Structs.

If we do not use inheritance, structs in Mojo are almost the same as classes in Python. So we can simply change the keyword class to struct:

mojo
struct Triangle:
    ...

There will be no error message for this line anymore, so do the next line, the docstring. Yes, Mojo supports exactly the same docstring syntax as Python. You can learn more about docstring in Section Documentation string.

The next error message is about the __init__ method.

console
error: __init__ method must return Self type with 'out' argument
    def __init__(self, a: Float64, b: Float64, c: Float64):
        ^

This method in Python is a special method to create an instance of the class by using the class name as a constructor, e.g., Triangle(3, 4, 5). In Mojo, we have the same philosophy, we also use __init__() as a constructor, but we have to explicitly specify the "ownership modifier" of the first argument self as out. This indicates that the __init__() method will create a new instance of the struct as an output. (out is a abbreviation of "output".)

We then update the first line of the __init__() method, by adding the out keyword before self:

mojo
...
def __init__(out self, a: Float64, b: Float64, c: Float64):
    ...
...

The next warning message is about the docstring of the __init__() method. It says:

console
unknown parameter 'a (float)' in doc string

This is because Mojo uses "argument" to refer to both Python's "parameter" and "argument". Mojo then use "parameter to refer to something else, a run-time constant. We will discuss more about the difference between argument and parameter in Chapter Functions and Chapter Parameterization.

For now, we just change the docstring to use "Args" instead of "Parameters". Moreover, we remove the type of the arguments in the docstring, because we have already specified the types in the function signature:

mojo
...
"""
Args:
    a: Length of side a.
    b: Length of side b.
    c: Length of side c.
"""
...

The next error message is the first line in the body of the __init__() method:

console
/Users/ZHU/Programs/my-first-mojo-project/src/move/triangle.mojo:15:13: error: 'Triangle' value has no attribute 'a'
        self.a = a
        ~~~~^

"What does this mean", you may ask?

As mentioned above, Mojo is a statically typed language. It does not allow you to create new attributes on the fly. You have to declare the attributes in the struct definition, so that Mojo can allocate the correct amount of memory for the struct instance. When the __init__() function is called, it will copy the values you pass in into the allocated memory.

So, we need to explicitly declare the attributes a, b, and c in the struct definition. This is done by using the var keyword before the attribute name, a colon : and the type of the attribute. The code after the change looks like this:

mojo
struct Triangle:
    """A struct to represent a triangle."""

    # Declare attributes
    var a: Float64
    var b: Float64
    var c: Float64
    
    ...

The next error message is about the error we raised when the three sides do not form a valid triangle:

console
/Users/ZHU/Programs/my-first-mojo-project/src/move/triangle.mojo:29:19: error: use of unknown declaration 'ValueError'
            raise ValueError("The lengths of sides do not form a valid triangle.")
                  ^~~~~~~~~~

This is because Mojo does not have the built-in exception ValueError like Python. Instead, Mojo has a more general exception called Error. So we change the line to:

mojo
raise Error("The lengths of sides do not form a valid triangle.")

After this change, you will be happy to see that there are no more error message in the body of the struct Triangle.


Then we come to the main function. The first error message is about printing the triangle:

console
error: invalid call to 'print': could not convert element of 'values' with type 'Triangle' to expected type 'Writable'
    print(triangle)
    ^~~~~
error: invalid call to 'print': could not convert element of 'values' with type 'Triangle' to expected type 'Writable'
        print(invalid_triangle)
        ^~~~~

Ah, it is the same error as we had in the previous example when we tried to print a list. The core message is "argument type 'Triangle' does not conform to trait 'Writable'". What does this mean? Maybe you have to wait until you reach Chapter Generic and traits. For now, you can understand this issue in the following way:

In Python, when you call print(triangle), Python interpreter will automatically call the __str__() method of the Triangle class to get a string representation of the triangle. That is to say, print(triangle) will be expanded to print(triangle.__str__()).

In Mojo, however, it is not possible. the __str__() method is only used for the purposes of converting the instance to a string, by calling the constructor String(triangle). In order to print out an instance, you have to implement another method called write_to() in the struct.

I am not going to write this write_to() method for you here because it covers some other concepts. A quick and easy workaround is to simply use the String() constructor to first convert the instance to a string, and then use the print() function to print the string. So we change the line to:

mojo
print(String(triangle))
...
print(String(invalid_triangle))

Moreover, for some special double underscore methods like __str__(), Mojo requires you to explicitly include the related trait in the struct signature. Here, the __str__() method is corresponding to the trait Writable. This knowledge is too advanced for beginners and we will discuss this in details in Chapter Generic and Traits. For now, we just add the trait name to the struct signature:

mojo
struct Triangle(Writable):
    ...

Finally, we come to the last error message, which is about try-except statement:

console
/Users/ZHU/Programs/my-first-mojo-project/src/move/triangle.mojo:73:18: error: expected ':' after 'except'
    except Error as e:
                 ^

The reason is simple: Mojo does not support the as keyword in the except clause. So we change the line to:

mojo
except e:
    print("Error:", e)

After all these changes, we have our final Mojo code as follows:

mojo
# src/move/triangle.mojo


struct Triangle(Writable):
    """A struct to represent a triangle."""

    # Declare attributes
    var a: Float64
    var b: Float64
    var c: Float64

    def __init__(out self, a: Float64, b: Float64, c: Float64) raises:
        """Initializes a triangle with three sides.

        Args:
            a: Length of side a.
            b: Length of side b.
            c: Length of side c.

        Raises:
            Error: If the lengths do not form a valid triangle.
        """
        self.a = a
        self.b = b
        self.c = c

        if (
            (self.a + self.b <= self.c)
            or (self.a + self.c <= self.b)
            or (self.b + self.c <= self.a)
        ):
            raise Error("The lengths of sides do not form a valid triangle.")

    def area(self) -> Float64:
        """Calculates the area of the triangle using Heron's formula.

        Returns:
            Float64: The area of the triangle.
        """
        var s = (self.a + self.b + self.c) / 2
        return (s * (s - self.a) * (s - self.b) * (s - self.c)) ** 0.5

    def perimeter(self) -> Float64:
        """Calculates the perimeter of the triangle.

        Returns:
            Float64: The perimeter of the triangle.
        """
        return self.a + self.b + self.c

    def __str__(self) raises -> String:
        """Returns a string representation of the triangle.

        Returns:
            A string representation of the triangle.

        Notes:
            You can use the `str()` or `print()` to call this method.
        """
        return "Triangle(a={}, b={}, c={})".format(self.a, self.b, self.c)


def main() raises:
    # A valid triangle with sides 3, 4, and 5
    print("Creating a valid triangle with sides 3, 4, and 5:")
    var triangle = Triangle(3, 4, 5)
    print(String(triangle))
    print("Area: {}".format(triangle.area()))
    print("Perimeter: {}".format(triangle.perimeter()))

    # An invalid triangle with sides 1, 2, and 3
    print("\nCreating an invalid triangle with sides 1, 2, and 3:")
    var invalid_triangle: Triangle
    try:
        invalid_triangle = Triangle(1, 2, 3)
        print(String(invalid_triangle))
    except e:
        print("Error:", e)
python
# src/move/triangle.py
class Triangle:
    """A class to represent a triangle."""

    # Declare attributes
    a: float
    b: float
    c: float

    def __init__(self, a: float, b: float, c: float):
        """Initializes a triangle with three sides.

        Parameters:
            a (float): Length of side a.
            b (float): Length of side b.
            c (float): Length of side c.

        Raises:
            ValueError: If the lengths do not form a valid triangle.
        """
        self.a = a
        self.b = b
        self.c = c

        if (
            (self.a + self.b <= self.c)
            or (self.a + self.c <= self.b)
            or (self.b + self.c <= self.a)
        ):
            raise ValueError("The lengths of sides do not form a valid triangle.")

    def area(self) -> float:
        """Calculates the area of the triangle using Heron's formula.

        Returns:
            float: The area of the triangle.
        """
        s = (self.a + self.b + self.c) / 2
        return (s * (s - self.a) * (s - self.b) * (s - self.c)) ** 0.5

    def perimeter(self) -> float:
        """Calculates the perimeter of the triangle.

        Returns:
            float: The perimeter of the triangle.
        """
        return self.a + self.b + self.c

    def __str__(self) -> str:
        """Returns a string representation of the triangle.

        Returns:
            A string representation of the triangle.

        Notes:
            You can use the `str()` or `print()` to call this method.
        """
        return f"Triangle(a={self.a}, b={self.b}, c={self.c})"


def main():
    # A valid triangle with sides 3, 4, and 5
    print("Creating a valid triangle with sides 3, 4, and 5:")
    triangle = Triangle(3, 4, 5)
    print(triangle)
    print(f"Area: {triangle.area()}")
    print(f"Perimeter: {triangle.perimeter()}")

    # An invalid triangle with sides 1, 2, and 3
    print("\nCreating an invalid triangle with sides 1, 2, and 3:")
    try:
        invalid_triangle = Triangle(1, 2, 3)
        print(invalid_triangle)
    except ValueError as e:
        print(f"Error: {e}")


main()
text
Creating a valid triangle with sides 3, 4, and 5:
Triangle(a=3.0, b=4.0, c=5.0)
Area: 6.000000000120229
Perimeter: 12.0

Creating an invalid triangle with sides 1, 2, and 3:
Error: The lengths of sides do not form a valid triangle.
text
Creating a valid triangle with sides 3, 4, and 5:
Triangle(a=3, b=4, c=5)
Area: 6.0
Perimeter: 12

Creating an invalid triangle with sides 1, 2, and 3:
Error: The lengths of sides do not form a valid triangle.

Compiling and running the code with pixi run mojo src/move/triangle.mojo generates no error messages.

This example is more complex than the previous ones, you may need some more time and effort to understand and adapt to the differences between Python and Mojo. But it is a good starting point to learn how to write Mojo code and how to use the features of Mojo to achieve the same functionality as in Python.

Difference between Python and Mojo

The table below summarizes the differences between Python and Mojo in this example.

FeaturePythonMojo
Define typeclass keywordstruct keyword
InheritanceSupportedNot supported
AttributesNo declaration neededDeclared in struct definition with keyword var
Argument of the type itselfselfself, but following the keyword out
Parameter vs argument in docstringParameters in docstringArgs in docstring
Exception classesSeveral built-in classesError is the only built-in exception class
print() calls__str__() methodwrite_to() method
__str__() is used forprint() and str()Only for string conversion via String()
Trait namesNot applicableMust be explicitly included in the struct signature
as in except statementSupportedNot supported

write_to() method

If you want to implement the write_to() method to print the triangle directly, you can do it like this:

mojo
def write_to[T: Writer](self, mut writer: T):
    """Writes the complex number to a writer."""
    writer.write("Triangle(a=", self.a, ", b=", self.b, ", c=", self.c, ")")

Then you can call print(triangle) directly without using String().

mojo
print(triangle)

This will print the triangle in the same way as print(String(triangle)):

console
Triangle(a=3.0, b=4.0, c=5.0)

Next step

With these four examples, you should already have a good idea of how Mojo code looks like and how it is similar to or different from Python. You also see how powerful Mojo is in terms of performance. In the next chapters, we will investigate the concepts that are in common between Mojo and Python, so that you do not need to change your coding habits.

Main changes in this chapter

  • 2026-08-19: Update to accommodate the changes in Mojo v1.0.0. print() now prints a list in Python's style, so the print_list() helper of Case 3 moves into a details block. A list literal now builds an Array, so Case 3 annotates its variable as a List.

Mojo Miji - A Guide to Mojo Programming Language from A Pythonista's Perspective · 魔咒秘籍 - Pythonista 視角下的 Mojo 編程語言指南