print("Hello, World!")def main() raises:
print("Hello, World!")Mojo's print() works exactly like Python's: it accepts any number of values, joins them with spaces, and adds a newline. The entry point is def main() raises: rather than the implicit top-level execution Python uses. The raises qualifier allows the function to propagate errors freely — a useful habit for main() so any called function can use raise without an additional declaration.
name = "Alice"
age = 30
print(name, age)
print(name, "is", age, "years old")def main() raises:
var name = "Alice"
var age = 30
print(name, age)
print(name, "is", age, "years old")Mojo's print() accepts multiple comma-separated arguments and joins them with spaces — identical to Python's print(). The only visible difference in this example is the var keyword on the first two lines. The multi-argument form is the idiomatic one, and it makes clear that no implicit type coercion is occurring.
# Single-line comment
print("Hello") # inline comment
"""
Multi-line string often
used as a block comment
"""def main() raises:
# Single-line comment
print("Hello") # inline comment
# Mojo has no block comment syntax —
# use consecutive line comments instead.Mojo uses # for line comments, identical to Python. A triple-quoted string on a line of its own is a docstring rather than a comment — the compiler reads it and even lints its wording — so use consecutive line comments for longer explanations.
value = 42
pi = 3.14159
print("Value: " + str(value))
print("Pi: " + str(pi))def main() raises:
var value: Int = 42
var pi: Float64 = 3.14159
print("Value: " + String(value))
print("Pi: " + String(pi))Python's built-in str() converts any value to a string for concatenation. Mojo uses an explicit String(value) constructor call that works the same way. Both produce a human-readable string representation. Alternatively, passing values as separate print() arguments avoids the conversion step entirely and is the more common Mojo idiom.
name = "Alice"
count = 42
active = True
print(name, count, active)def main() raises:
var name = "Alice"
var count = 42
var active = True
print(name, count, active)Mojo introduces a new variable with the var keyword. Bare assignment to an undeclared name still compiles, but the compiler warns that implicit declaration is deprecated — where Python accepts it silently. Types are inferred from the initial value exactly as in Python, so no annotation is needed when the type is obvious from context.
name: str = "Alice"
age: int = 30
score: float = 9.5
print(name, age, score)def main() raises:
var name: String = "Alice"
var age: Int = 30
var score: Float64 = 9.5
print(name, age, score)Python's type hints (name: str = "Alice") are advisory — the interpreter ignores them at runtime. Mojo's type annotations are enforced at compile time: assigning the wrong type is a compile error. Mojo uses String (capital S) rather than Python's str, and Float64 rather than float. Plain Int is machine-word-sized (64-bit on modern hardware).
counter = 0
counter = 10
counter += 5
print(counter) # 15def main() raises:
var counter = 0
counter = 10
counter += 5
print(counter) # 15Once a var variable is declared, reassignment uses plain = without the var keyword — the same as Python. Compound assignment operators (+=, -=, *=, /=) all work as expected. The difference from Python is only at the point of initial declaration, where var is required.
# Python has no built-in constant mechanism;
# convention is to use ALL_CAPS names (but nothing enforces it)
MAX_CONNECTIONS = 100
APP_NAME = "MyApp"
print(MAX_CONNECTIONS, APP_NAME)comptime MAX_CONNECTIONS: Int = 100
comptime APP_NAME: String = "MyApp"
def main() raises:
print(MAX_CONNECTIONS, APP_NAME)Mojo's comptime keyword creates a compile-time constant — the value is substituted at compile time and cannot be changed at runtime. (Older material writes alias, which still compiles with a deprecation warning.) Python only has a naming convention (ALL_CAPS) that other programmers are supposed to respect; nothing prevents reassignment. A comptime value can appear in type parameter positions where only compile-time-known values are allowed, such as SIMD vector widths.
whole = 10
decimal = float(whole) / 3.0
text = str(whole)
print(decimal)
print(text + " items")def main() raises:
var whole: Int = 10
var decimal: Float64 = Float64(whole) / 3.0
var text: String = String(whole)
print(decimal)
print(text + " items")Python's built-in conversion functions (int(), float(), str()) accept almost any value and handle conversion automatically. Mojo uses explicit constructor-style calls: Float64(whole), String(whole). Mojo never performs implicit numeric coercion — mixing Int and Float64 in an expression without an explicit conversion is a compile error.
greeting = "Hello"
name = "World"
combined = greeting + ", " + name + "!"
print(combined)
print(len(combined))def main() raises:
var greeting = "Hello"
var name = "World"
var combined = greeting + ", " + name + "!"
print(combined)
print(combined.byte_length())Mojo's String type behaves much like Python's: + concatenates strings, and len() returns the byte length. All Mojo strings are UTF-8 encoded and immutable by default, matching Python's string immutability. One key difference: Mojo's len() counts bytes, not Unicode code points, so multi-byte characters produce a larger count than Python's character-counting len().
message = "Hello, World!"
print(message.upper())
print(message.lower())def main() raises:
var message = "Hello, World!"
print(message.upper())
print(message.lower())Mojo's String type provides the same .upper() and .lower() methods as Python. Both return a new string without modifying the original — string immutability is the same in both languages. These method names were intentionally chosen to match Python's, easing the transition.
sentence = "The quick brown fox"
print("quick" in sentence)
print(sentence.find("fox"))
print(sentence.replace("fox", "cat"))def main() raises:
var sentence = "The quick brown fox"
print("quick" in sentence)
print(sentence.find("fox"))
print(sentence.replace("fox", "cat"))Mojo's .find() returns the byte index of the first occurrence, or -1 if not found — the same semantics as Python's str.find(). Containment is the in operator, exactly as in Python. The .replace() method replaces all occurrences, matching Python's behavior.
padded = " hello "
cleaned = padded.strip()
print(cleaned)
words = "one two three".split()
print(len(words))def main() raises:
var padded = " hello "
var cleaned = padded.strip()
print(cleaned)
var words = "one two three".split()
print(len(words)).strip() removes leading and trailing whitespace and .split() without arguments splits on whitespace runs — both behave identically to Python. Mojo's .split() returns a List[String] rather than Python's list, but the usage is the same: index it with [i] or iterate with a for loop.
filename = "report.csv"
print(filename.startswith("report"))
print(filename.endswith(".csv"))
print(filename.endswith(".txt"))def main() raises:
var filename = "report.csv"
print(filename.startswith("report"))
print(filename.endswith(".csv"))
print(filename.endswith(".txt")).startswith() and .endswith() have identical names and semantics in Mojo and Python. Both return a Bool value. This is one of the areas where Mojo's Python-superset design makes the transition completely seamless — the code is character-for-character identical.
# Python has one integer type: arbitrary precision
small = 42
large = 10 ** 100 # googol — no overflow in Python
print(type(small).__name__)
print(small)def main() raises:
var machine_word: Int = 42
var signed_64: Int64 = 1_000_000_000
var signed_32: Int32 = 32_767
var signed_8: Int8 = 127
print(machine_word)
print(signed_64)Python's int has arbitrary precision — it grows as large as memory allows. Mojo's integer types have fixed bit widths: Int8, Int16, Int32, Int64, and their unsigned counterparts (UInt8, etc.). Plain Int is machine-word-sized (64-bit on a 64-bit system). Unlike Python, Mojo integers can overflow — an Int8 holding 128 wraps in release builds and panics in debug builds.
# Python has one float type: IEEE 754 double-precision (64-bit)
pi = 3.14159265358979
tau = 6.28318530717958
print(type(pi).__name__)
print(pi + tau)def main() raises:
var pi: Float64 = 3.14159265358979
var approx: Float32 = 3.14
var half_prec: Float16 = 3.14
print(pi)
print(approx)Python's float is always IEEE 754 double-precision (64-bit). Mojo provides three floating-point types: Float16, Float32, and Float64. Floating-point literals default to Float64, matching Python's behavior. Use Float32 or Float16 to match the precision expected by GPU and SIMD hardware in machine-learning workloads — the primary domain Mojo targets.
a = 17
b = 5
print(a + b) # 22
print(a - b) # 12
print(a * b) # 85
print(a // b) # 3 (floor division)
print(a % b) # 2 (modulo)
print(a ** 2) # 289 (exponentiation)def main() raises:
var a = 17
var b = 5
print(a + b) # 22
print(a - b) # 12
print(a * b) # 85
print(a // b) # 3 (floor division)
print(a % b) # 2 (modulo)
print(a ** 2) # 289 (exponentiation)Mojo's arithmetic operators are identical to Python's: +, -, *, // (floor division), % (modulo), and ** (exponentiation). This is intentional — Mojo is a Python superset. The difference is that Mojo resolves these at compile time when types are known, potentially emitting a single CPU instruction instead of a Python bytecode dispatch chain.
is_active = True
is_done = False
print(is_active)
print(not is_active)
print(is_active and not is_done)def main() raises:
var is_active: Bool = True
var is_done: Bool = False
print(is_active)
print(not is_active)
print(is_active and not is_done)Mojo's Bool type uses the same capitalized literals (True, False) and the same English-word operators (and, or, not) as Python. This is one of the areas where the transition from Python to Mojo is effortless — the code looks identical. The difference is that Bool in Mojo is a strictly typed value, not a subclass of int as it is in Python.
numbers = [10, 20, 30, 40]
print(numbers[0]) # 10
print(numbers[-1]) # 40
print(len(numbers)) # 4def main() raises:
var numbers: List[Int] = [10, 20, 30, 40]
print(numbers[0]) # 10
print(numbers[len(numbers) - 1]) # 40
print(len(numbers)) # 4Mojo's List[T] is a typed, resizable array. The literal [10, 20, 30, 40] works as in Python once the variable's element type is declared, and every element must have that type. Lists are zero-indexed like Python's, but a negative index is rejected — numbers[-1] is a compile error that suggests numbers[len(numbers) - 1].
fruits = ["apple", "banana", "cherry"]
for fruit in fruits:
print(fruit)
for index, fruit in enumerate(fruits):
print(index, fruit)def main() raises:
var fruits: List[String] = ["apple", "banana", "cherry"]
for fruit in fruits:
print(fruit)
for index, fruit in enumerate(fruits):
print(index, fruit)Both loops carry over unchanged: for fruit in fruits yields each element, and enumerate() yields index-and-value pairs exactly as Python's does.
scores = {"Alice": 95, "Bob": 87}
print(scores["Alice"]) # 95
scores["Charlie"] = 92
print(len(scores)) # 3def main() raises:
var scores: Dict[String, Int] = {"Alice": 95, "Bob": 87}
print(scores["Alice"]) # 95
scores["Charlie"] = 92
print(len(scores)) # 3Dict[K, V] is Mojo's typed hash map. The dict literal {"Alice": 95, "Bob": 87} works as in Python once the variable declares its key and value types. Subscript access and assignment (dict["key"] = value) work identically to Python. Accessing a missing key raises an error in Mojo — use .get() for safe access with a default value.
config = {"host": "localhost", "port": "5432"}
host = config.get("host", "127.0.0.1")
timeout = config.get("timeout", "30")
print(host)
print(timeout)def main() raises:
var config = Dict[String, String]()
config["host"] = "localhost"
config["port"] = "5432"
var host = config.get("host", "127.0.0.1")
var timeout = config.get("timeout", "30")
print(host)
print(timeout)Mojo's Dict.get(key, default) returns the value for key if present, otherwise returns default — exactly the same semantics as Python's dict.get(key, default). This is one of the many areas where Mojo's Python-superset design means existing Python knowledge transfers directly without adjustment.
grades = {"Alice": "A", "Bob": "B", "Charlie": "C"}
for name, grade in grades.items():
print(name, grade)def main() raises:
var grades = Dict[String, String]()
grades["Alice"] = "A"
grades["Bob"] = "B"
grades["Charlie"] = "C"
for key in grades.keys():
var name = String(key)
print(name, grades[name])Python's dict.items() yields key-value pairs in a single loop. Mojo's Dict provides .keys() and .values() iterators, but not .items() in current versions. Iterating over .keys() yields references — copy each key with String(key) before using it as a subscript argument to avoid aliasing errors from the iterator holding a reference into the dict's own memory.
score = 85
if score >= 90:
print("A")
elif score >= 80:
print("B")
elif score >= 70:
print("C")
else:
print("F")def main() raises:
var score = 85
if score >= 90:
print("A")
elif score >= 80:
print("B")
elif score >= 70:
print("C")
else:
print("F")Mojo's conditional syntax is identical to Python's: if, elif, else, colon-terminated conditions, and indentation-based blocks. Comparison operators (>=, ==, !=, <, >, <=) are the same as Python's. This is a zero-adjustment area for Python programmers moving to Mojo.
for number in range(1, 6):
print(number)
for number in range(0, 10, 2):
print(number)def main() raises:
for number in range(1, 6):
print(number)
for number in range(0, 10, 2):
print(number)Mojo's range(start, stop, step) is functionally identical to Python's: stop is exclusive and the three-argument form sets the step. The for number in range(...) syntax is the same in both languages. This example is shown to confirm that Python intuition transfers completely — the code is literally identical.
countdown = 5
while countdown > 0:
print(countdown)
countdown -= 1
print("Go!")def main() raises:
var countdown = 5
while countdown > 0:
print(countdown)
countdown -= 1
print("Go!")Mojo's while loop syntax is identical to Python's. The only visible difference in this example is var countdown on the first line — once declared, countdown behaves exactly as in Python. Python's while/else carries over too: the else block runs when the loop ends without a break.
for number in range(1, 11):
if number % 2 == 0:
continue
if number > 7:
break
print(number)def main() raises:
for number in range(1, 11):
if number % 2 == 0:
continue
if number > 7:
break
print(number)break and continue behave identically in Python and Mojo — no adjustment needed. This example is shown specifically to confirm that Python loop-control intuition transfers directly. The code is character-for-character identical inside def main() raises:.
colors = ["red", "green", "blue"]
for index, color in enumerate(colors):
print(index, color)def main() raises:
var colors: List[String] = ["red", "green", "blue"]
for index, color in enumerate(colors):
print(index, color)Python's enumerate() carries over exactly: it yields index-and-value pairs, unpacked in the for header.
def greet(name: str) -> str:
return f"Hello, {name}!"
print(greet("Alice"))def greet(name: String) -> String:
return "Hello, " + name + "!"
def main() raises:
print(greet("Alice"))Mojo's def looks like Python's and checks what Python's does not: every parameter needs a type, a function that can raise must say raises, and both are enforced at compile time. The body uses the same return-value semantics as Python — the return keyword is required (unlike Ruby).
def greet(name: str = "World", punctuation: str = "!") -> str:
return f"Hello, {name}{punctuation}"
print(greet())
print(greet("Alice"))
print(greet("Bob", "."))def greet(name: String = "World", punctuation: String = "!") -> String:
return "Hello, " + name + punctuation
def main() raises:
print(greet())
print(greet("Alice"))
print(greet("Bob", "."))Default argument syntax is nearly identical between Python and Mojo's def functions — the same param: Type = default form. A call may also name its arguments, as in Python: greet(punctuation=".") keeps the default name.
def compute_bounds(numbers: list[int]) -> tuple[int, int]:
return min(numbers), max(numbers)
low, high = compute_bounds([3, 1, 4, 1, 5, 9, 2])
print(low, high)def find_min_max(first: Int, second: Int, third: Int) -> Tuple[Int, Int]:
var minimum = first
var maximum = first
if second < minimum:
minimum = second
if second > maximum:
maximum = second
if third < minimum:
minimum = third
if third > maximum:
maximum = third
return (minimum, maximum)
def main() raises:
var low, high = find_min_max(3, 7, 1)
print(low, high)Both languages return several values as a tuple and unpack it at the call site. Mojo spells the return type out — Tuple[Int, Int] — and the unpacking declares its names with var: var low, high = find_min_max(3, 7, 1).
def factorial(number: int) -> int:
if number <= 1:
return 1
return number * factorial(number - 1)
print(factorial(6))def factorial(number: Int) -> Int:
if number <= 1:
return 1
return number * factorial(number - 1)
def main() raises:
print(factorial(6))Recursive def functions look nearly identical in Python and Mojo. The only change here is the Mojo type name Int (capital I) instead of Python's int. Both languages use the same recursive call syntax.
# Python type hints are advisory — ignored at runtime
def add(x: int, y: int) -> int:
return x + y
print(add(3, 4))
print(add(1.5, 2.5)) # Works — Python coerces float to int silently? No, it just runsdef add(left: Int, right: Int) -> Int:
return left + right
def main() raises:
print(add(3, 4))
# add(1.5, 2.5) would be a compile-time errorMojo and Python share the def keyword and mean quite different things by it. Python's type hints are advisory and ignored at runtime — def add(x: int, y: int) still happily accepts a float. Mojo requires the annotations and enforces them: passing the wrong type is a compile error, which is what allows dispatch with no runtime type checks. (Mojo used to spell the strict form fn and keep def loose; 1.0 removed fn and made def the strict one.)
# Python silently allows functions to mutate mutable arguments
def double_in_place(numbers: list[int]) -> None:
for index in range(len(numbers)):
numbers[index] *= 2
data = [1, 2, 3]
double_in_place(data)
print(data) # [2, 4, 6]def double_in_place(mut numbers: List[Int]):
for index in range(len(numbers)):
numbers[index] = numbers[index] * 2
def main() raises:
var data = List[Int]()
data.append(1)
data.append(2)
data.append(3)
double_in_place(data)
for index in range(len(data)):
print(data[index])Python silently allows a function to mutate any mutable object passed to it — there is no indication at the call site or in the signature that mutation will occur. Mojo requires the mut keyword on the parameter to declare that mutation is intended. Without mut, the parameter is borrowed immutably and cannot be modified. A reader can see from the signature whether a function mutates its arguments, making the contract explicit.
def parse_age(text: str) -> int:
age = int(text)
if age < 0:
raise ValueError("Age cannot be negative")
return age
print(parse_age("25"))def parse_age(text: String) raises -> Int:
var age = atol(text)
if age < 0:
raise Error("Age cannot be negative")
return age
def main() raises:
print(parse_age("25"))In Python, any function can raise any exception at any time — nothing in the signature indicates this. A Mojo function must declare raises if it can propagate an error. This makes the error contract visible and compiler-checked. atol() is Mojo's built-in for parsing a String to Int — it raises automatically if parsing fails, which is why parse_age must also declare raises.
# Python uses isinstance() checks instead of overloading
def describe(value: int | float) -> str:
if isinstance(value, int):
return f"Integer: {value}"
return f"Float: {value}"
print(describe(42))
print(describe(3.14))def describe(value: Int) -> String:
return "Integer: " + String(value)
def describe(value: Float64) -> String:
return "Float: " + String(value)
def main() raises:
print(describe(42))
print(describe(3.14))Mojo supports function overloading: several def definitions with the same name but different parameter types. The compiler selects the correct version at compile time based on the argument types. Python achieves similar behavior by checking isinstance() at runtime — Mojo does the same dispatch statically with no runtime cost and without the branching code in the function body.
from dataclasses import dataclass
@dataclass
class Point:
x: float
y: float
point = Point(3.0, 4.0)
print(point.x, point.y)@fieldwise_init
struct Point(Copyable, Movable):
var x: Float64
var y: Float64
def main() raises:
var point = Point(3.0, 4.0)
print(point.x, point.y)Python's @dataclass and Mojo's @fieldwise_init both auto-generate a constructor that accepts one argument per field in declaration order. The critical difference is value semantics: assigning a Mojo struct copies it, whereas assigning a Python object copies a reference. Copyable and Movable are built-in traits that must be listed explicitly to enable copying and moving the struct.
class Rectangle:
def __init__(self, width: float, height: float):
self.width = width
self.height = height
rect = Rectangle(3.0, 4.0)
print(rect.width)struct Rectangle:
var width: Float64
var height: Float64
def __init__(out self, width: Float64, height: Float64):
self.width = width
self.height = height
def main() raises:
var rectangle = Rectangle(3.0, 4.0)
print(rectangle.width)Mojo structs use def __init__(out self, ...) for explicit constructors — the out self parameter indicates that this function initializes the struct rather than receiving a pre-allocated instance. Python's __init__(self, ...) receives an already-allocated object. Both set fields via self.field = value. Mojo field declarations (var width: Float64) must appear explicitly in the struct body before they can be assigned in __init__.
import math
class Circle:
def __init__(self, radius: float):
self.radius = radius
def area(self) -> float:
return math.pi * self.radius ** 2
circle = Circle(5.0)
print(round(circle.area(), 4))from std.math import pi
struct Circle:
var radius: Float64
def __init__(out self, radius: Float64):
self.radius = radius
def area(self) -> Float64:
return pi * self.radius * self.radius
def main() raises:
var circle = Circle(5.0)
print(circle.area())Read-only methods take self (borrowed, immutable) — matching Python's convention where self refers to the current instance. Mojo imports standard library items explicitly, as Python does, from under std: from std.math import pi. Methods are called with instance.method() and always require parentheses, even for zero-argument methods.
class Counter:
def __init__(self):
self.count = 0
def increment(self):
self.count += 1
def value(self) -> int:
return self.count
counter = Counter()
counter.increment()
counter.increment()
print(counter.value()) # 2struct Counter:
var count: Int
def __init__(out self):
self.count = 0
def increment(mut self):
self.count += 1
def value(self) -> Int:
return self.count
def main() raises:
var counter = Counter()
counter.increment()
counter.increment()
print(counter.value()) # 2In Python, any method can modify instance attributes — self is always a mutable reference. Mojo requires methods that modify the struct to declare mut self instead of self. Read-only methods use plain self (borrowed). This distinction is enforced by the compiler: calling a mut self method on an immutable binding is a compile error, making mutation intent explicit in the API.
class Person:
def __init__(self, name: str, age: int):
self.name = name
self.age = age
def __str__(self) -> str:
return f"Person({self.name}, {self.age})"
person = Person("Alice", 30)
print(person) # __str__ called automaticallystruct Person(Writable):
var name: String
var age: Int
def __init__(out self, name: String, age: Int):
self.name = name
self.age = age
def write_to(self, mut writer: Some[Writer]):
writer.write("Person(" + self.name + ", " + String(self.age) + ")")
def main() raises:
var person = Person("Alice", 30)
print(String(person)) # String() still works; print(person) alone would tooPython calls __str__ automatically when an object is passed to print(). Mojo uses the Writable trait (listed in parentheses after the struct name) with a write_to(self, mut writer) method that writes into the writer rather than returning a String. String(instance) converts the struct to a string explicitly, but print(instance) also works directly since Person conforms to Writable.
from abc import ABC, abstractmethod
class Greetable(ABC):
@abstractmethod
def greet(self) -> str:
...trait Greetable:
def greet(self) -> String: ...
def main() raises:
passMojo's trait keyword is the equivalent of Python's ABC with @abstractmethod. A trait declares required method signatures — structs that list the trait must implement every method, or the compiler rejects the code. Unlike Python ABCs, Mojo traits are checked at compile time with no runtime overhead. A trait may also give a method a default body, which a struct inherits unless it defines its own.
from abc import ABC, abstractmethod
class Describable(ABC):
@abstractmethod
def describe(self) -> str: ...
class Dog(Describable):
def __init__(self, name: str):
self.name = name
def describe(self) -> str:
return f"Dog named {self.name}"
print(Dog("Rex").describe())trait Describable:
def describe(self) -> String: ...
@fieldwise_init
struct Dog(Describable, Copyable, Movable):
var name: String
def describe(self) -> String:
return "Dog named " + self.name
def main() raises:
var dog = Dog("Rex")
print(dog.describe())A Mojo struct implements a trait by listing it in parentheses after the struct name, alongside built-in traits like Copyable and Movable. The compiler verifies that all trait methods are implemented — unlike Python ABCs, where a class can be instantiated with abstract methods still missing and only fail at runtime when those methods are called. Multiple traits are listed together in one set of parentheses.
from abc import ABC, abstractmethod
class Shape(ABC):
@abstractmethod
def area(self) -> float: ...
class Square(Shape):
def __init__(self, side: float):
self.side = side
def area(self) -> float:
return self.side ** 2
class Circle(Shape):
def __init__(self, radius: float):
self.radius = radius
def area(self) -> float:
import math
return math.pi * self.radius ** 2
for shape in [Square(4.0), Circle(3.0)]:
print(round(shape.area(), 4))from std.math import pi
trait Shape:
def area(self) -> Float64: ...
@fieldwise_init
struct Square(Shape, Copyable, Movable):
var side: Float64
def area(self) -> Float64:
return self.side * self.side
@fieldwise_init
struct Circle(Shape, Copyable, Movable):
var radius: Float64
def area(self) -> Float64:
return pi * self.radius * self.radius
def print_area[ShapeType: Shape](shape: ShapeType):
print(shape.area())
def main() raises:
var square = Square(4.0)
var circle = Circle(3.0)
print_area(square)
print_area(circle)Mojo achieves polymorphism through parametric functions: def print_area[ShapeType: Shape](shape: ShapeType) accepts any type ShapeType that implements the Shape trait. The square-bracket [ShapeType: Shape] is a compile-time type parameter — the compiler generates a specialized version for each concrete type used, like C++ templates. Python achieves the same result through runtime duck typing. Mojo's approach produces identical behavior with zero runtime dispatch overhead.
from functools import total_ordering
@total_ordering
class Temperature:
def __init__(self, celsius: float):
self.celsius = celsius
def __eq__(self, other) -> bool:
return self.celsius == other.celsius
def __lt__(self, other) -> bool:
return self.celsius < other.celsius
boiling = Temperature(100.0)
body = Temperature(37.0)
print(boiling > body) # True@fieldwise_init
struct Temperature(Copyable, Movable, Comparable):
var celsius: Float64
def __lt__(self, other: Temperature) -> Bool:
return self.celsius < other.celsius
def __eq__(self, other: Temperature) -> Bool:
return self.celsius == other.celsius
def main() raises:
var boiling = Temperature(100.0)
var body = Temperature(37.0)
print(boiling > body) # TrueMojo's Comparable makes the same bargain as Python's @total_ordering: implement __lt__ and __eq__, and the other four comparisons come with the trait.
def check_positive(number: int) -> int:
if number <= 0:
raise ValueError("Must be positive")
return number
print(check_positive(5))def check_positive(number: Int) raises -> Int:
if number <= 0:
raise Error("Must be positive")
return number
def main() raises:
print(check_positive(5))Mojo's raise Error("message") is the equivalent of Python's raise ValueError("message"). Error carries a message string and is the everyday choice; a function may instead declare a type of its own — def check(value: Int) raises TooSmall — and raise values of that struct. The function signature must include raises to declare that errors may propagate from this function.
def risky_divide(dividend: float, divisor: float) -> float:
if divisor == 0:
raise ZeroDivisionError("Cannot divide by zero")
return dividend / divisor
try:
print(risky_divide(10.0, 2.0))
print(risky_divide(5.0, 0.0))
except ZeroDivisionError as error:
print("Caught:", error)def risky_divide(dividend: Float64, divisor: Float64) raises -> Float64:
if divisor == 0.0:
raise Error("Cannot divide by zero")
return dividend / divisor
def main() raises:
try:
print(risky_divide(10.0, 2.0))
print(risky_divide(5.0, 0.0))
except error:
print("Caught:", error)Mojo uses the same try / except syntax as Python. The caught value in except error: has the type the called function raises — Error here. A function that declares its own error type, raises TooSmall, hands the except block a TooSmall, whose fields it can read.
def read_config(path: str) -> str:
if path != "app.yaml":
raise FileNotFoundError(f"File not found: {path}")
return "config content"
def initialize_app() -> str:
config = read_config("app.yaml") # exception propagates automatically
return "App initialized with " + config
try:
print(initialize_app())
except FileNotFoundError as error:
print("Setup failed:", error)def read_config(path: String) raises -> String:
if path != "app.yaml":
raise Error("File not found: " + path)
return "config content"
def initialize_app() raises -> String:
var config = read_config("app.yaml") # error propagates automatically
return "App initialized with " + config
def main() raises:
try:
print(initialize_app())
except error:
print("Setup failed:", error)When a raises function calls another raises function without a try block, the error propagates up the call stack automatically — exactly as Python exceptions propagate through uncaught call frames. Mojo's raises annotation on each function in the chain makes the propagation path compiler-verified: a function that calls a raises function must itself declare raises or wrap the call in try/except.
class BankAccount:
def __init__(self, balance: float):
self.balance = balance
def withdraw(self, amount: float) -> float:
if amount > self.balance:
raise ValueError("Insufficient funds")
self.balance -= amount
return self.balance
account = BankAccount(100.0)
print(account.withdraw(40.0))struct BankAccount:
var balance: Float64
def __init__(out self, balance: Float64):
self.balance = balance
def withdraw(mut self, amount: Float64) raises -> Float64:
if amount > self.balance:
raise Error("Insufficient funds")
self.balance -= amount
return self.balance
def main() raises:
var account = BankAccount(100.0)
print(account.withdraw(40.0))A Mojo struct method can combine multiple qualifiers: mut self means the method may mutate the struct, and raises means it may propagate an error. Python methods always have implicit mutation access and can raise anything — Mojo makes both properties explicit in the signature. A caller can see at a glance what a method does to state and whether it can fail.
def process(data: str) -> str:
if not data:
raise ValueError("Empty data")
return data.upper()
try:
result = process("hello")
print("Success:", result)
except ValueError as error:
print("Error:", error)
finally:
print("Always runs")def process(data: String) raises -> String:
if data.byte_length() == 0:
raise Error("Empty data")
return data.upper()
def main() raises:
try:
var result = process("hello")
print("Success:", result)
except error:
print("Error:", error)
finally:
print("Always runs")Mojo has finally and it runs on the same paths Python's does — body succeeded, exception raised and caught, or an early return. It also has with, driven by __enter__ and __exit__ exactly as Python's context managers are. Both were measured on Mojo 1.0; older Mojo material says neither exists and points you at destructors instead. Destructors are still there and still run when a value goes out of scope, which remains the compile-time analogue of a context manager — but they are no longer the only way to guarantee cleanup.
# Python has no built-in SIMD; this is equivalent serial computation
prices = [10.0, 20.0, 30.0, 40.0]
discounted = [price * 0.9 for price in prices]
print(discounted)def main() raises:
var prices = SIMD[DType.float64, 4](10.0, 20.0, 30.0, 40.0)
var discounted = prices * 0.9
print(discounted)SIMD[DType, width] is a vector type that maps directly to CPU SIMD instructions. The operation prices * 0.9 applies the discount to all four elements in a single CPU instruction instead of a loop. The width must be a power of two. This is one of Mojo's headline features for ML developers: vectorized math written with familiar operators — no NumPy import required for simple cases, no gap between the code and the machine instructions.
# Python serial equivalent
vector_a = [1.0, 2.0, 3.0, 4.0]
vector_b = [5.0, 6.0, 7.0, 8.0]
combined = [a + b for a, b in zip(vector_a, vector_b)]
total = sum(combined)
print(total) # 36.0def main() raises:
var vector_a = SIMD[DType.float64, 4](1.0, 2.0, 3.0, 4.0)
var vector_b = SIMD[DType.float64, 4](5.0, 6.0, 7.0, 8.0)
var combined = vector_a + vector_b
var total = combined.reduce_add()
print(total) # 36.0SIMD types support all standard arithmetic operators element-wise: +, -, *, /. The .reduce_add() method sums all elements into a scalar, equivalent to Python's sum(). On modern hardware, the four-element addition is performed in a single CPU instruction. The Python equivalent requires a list comprehension and zip() — clear and readable, but many times slower for large numerical arrays.
# Python determines sizes and types at runtime
def sum_batch(values: list[float]) -> float:
return sum(values)
batch = [10.0, 20.0, 30.0, 40.0]
print(sum_batch(batch)) # 100.0comptime BATCH_SIZE: Int = 4
def sum_batch[batch_size: Int](values: SIMD[DType.float64, batch_size]) -> Float64:
return values.reduce_add()
def main() raises:
var batch = SIMD[DType.float64, BATCH_SIZE](10.0, 20.0, 30.0, 40.0)
print(sum_batch[BATCH_SIZE](batch)) # 100.0Square-bracket parameters like [batch_size: Int] are resolved at compile time — the compiler generates specialized code for each value of batch_size. Python determines everything at runtime: the list length, the element type, the function dispatch. Mojo's compile-time parameters allow the compiler to emit SIMD instructions of exactly the right width with no runtime branching. comptime constants serve as named compile-time values usable as type parameters.
import numpy as np
array = np.array([1.0, 2.0, 3.0, 4.0])
print(array.mean()) # 2.5from python import Python
def main() raises:
var numpy = Python.import_module("numpy")
var array = numpy.array([1.0, 2.0, 3.0, 4.0])
print(array.mean()) # 2.5Mojo can import and call any Python library directly via Python.import_module(). This requires a CPython installation to be available at runtime, which the servers that compile the Mojo here do not have — which is why there is nothing to run. In a full Mojo installation, this works seamlessly: you can mix Mojo's performance-critical code with the entire Python ecosystem — NumPy, pandas, scikit-learn, PyTorch — in a single file, gradually replacing hot paths with typed Mojo functions and SIMD operations.