For the complete Mojo documentation index, see llms.txt. Markdown versions of all pages are available by appending .md to any URL (e.g. /docs/manual/basics.md).
Mojo language basics
This page provides an overview of the Mojo language.
If you know Python, then a lot of Mojo code looks familiar. However, Mojo incorporates features like static type checking, memory safety, next-generation compiler technologies, and more. As such, Mojo also has a lot in common with languages like C++ and Rust.
If you prefer to learn by doing, follow the Get started with Mojo tutorial.
On this page, we'll introduce the essential Mojo syntax, so you can start coding quickly and understand other Mojo code you encounter. Subsequent sections in the Mojo Manual dive deeper into these topics, and this page links to them as appropriate.
Let's get started! 🔥
Hello world​
Here's the traditional "Hello world" program in Mojo:
def main():
print("Hello, world!")
Every Mojo program must include a function named main() as the entry point.
We'll talk more about functions soon, but for now it's enough to know that
you can write def main(): followed by an indented function body.
The print() function does what you'd expect,
printing its arguments to the standard output.
This page omits def main(): for many brief examples.
To test these, add them to a main() function.
Variables​
In Mojo, you can declare a variable using the var keyword:
def main():
var x = 10
var y = x * x
print(y)
You can also explicitly declare the variable type, with or without an assignment:
def main():
var x: Int = 10
var sum: Int
sum = x + x
Mojo variables are statically typed: that is, Mojo sets a variable's type at compile time, and the type doesn't change at runtime. If you don't specify a type, Mojo uses the type of the first value assigned to the variable.
var x = 10
x = "Foo" # Error: cannot implicitly convert 'StringLiteral["Foo"]' value to 'Int'
For more details, see the page about variables.
Blocks and statements​
Define code blocks such as functions, conditions, and loops with a colon followed by indented lines. For example:
def loop():
for x in range(5):
if x % 2 == 0:
print(x)
You can use any number of spaces or tabs for your indentation (we prefer 4 spaces).
All code statements in Mojo end with a newline. The Mojo compiler is fairly
lenient in allowing extra line breaks. As a rule of thumb, you can always break
statements between a pair of parentheses (()), square brackets ([]), or
curly braces ({}):
matrix_multiply(
matrix_a,
matrix_b,
result_matrix
)
You can add parentheses to continue a statement across lines:
var long_text = (
"This is a long line of text that is a lot easier to read if"
" it is broken up across two lines instead of one long line."
)
Mojo combines adjacent string literals, so long_text ends up with
a single, combined string.
For more information on loops and conditional statements, see Control flow.
Functions​
Define Mojo functions with the def keyword. For example, the
following uses the def keyword to define a function named greet()
that requires a single String
argument and returns a String:
def greet(name: String) -> String:
return "Hello, " + name + "!"
Code comments​
You can create a one-line comment using the hash # symbol:
# This is a comment. The Mojo compiler ignores this line.
Comments may also follow some code:
var message = "Hello, World!" # This is also a valid comment
Enclose API documentation comments in triple quotes. For example:
def print(x: String):
"""Prints a string.
Args:
x: The string to print.
"""
...
Documenting your code with these kinds of comments (known as "docstrings")
is a topic we've yet to fully specify, but you can generate an API reference
from docstrings using the mojo doc command.
Structs​
You can build high-level abstractions for types (or "objects") as a struct.
A struct in Mojo is similar to a class in Python: they both support
methods, fields, operator overloading, decorators for metaprogramming, and so
on. However, Mojo structs are completely static—the compiler binds them at
compile time, so they don't allow dynamic dispatch or any runtime changes to
the structure.
(Mojo will also support Python-style classes in the future.)
For example, here's a basic struct:
struct MyPair(Copyable):
var first: Int
var second: Int
def __init__(out self, first: Int, second: Int):
self.first = first
self.second = second
def __init__(out self, *, copy: Self):
self.first = copy.first
self.second = copy.second
def dump(self):
print(self.first, self.second)
And here's how you can use it:
def use_mypair():
var mine = MyPair(2, 4)
mine.dump()
The MyPair struct contains two special methods, __init__(), the constructor,
and __init__(out self, *, copy: Self), the copy constructor.
Lifecycle methods like this control how Mojo creates, copies, moves, and
destroys a struct.
For most simple types, you don't need to write the lifecycle methods. You can
use the @fieldwise_init
decorator to generate the boilerplate field-wise initializer for you, and Mojo
synthesizes copy and move constructors if you ask for them with trait
conformance. So you can simplify the MyPair struct to this:
@fieldwise_init
struct MyPair(Copyable):
var first: Int
var second: Int
def dump(self):
print(self.first, self.second)
For more details, see the page about structs.
Traits​
A trait is like a template of characteristics for a struct. If you want to create a struct with the characteristics defined in a trait, you must implement each characteristic (such as each method). Each characteristic in a trait is a "requirement" for the struct, and when your struct implements all of the requirements, it "conforms" to the trait.
Using traits allows you to write parameterized functions that can accept any type that conforms to a trait, rather than accepting only specific types.
For example, here's how you can create a trait:
trait SomeTrait:
def required_method(self, x: Int): ...
The three dots following the method signature are Mojo syntax indicating that the method has no implementation.
Here's a struct that conforms to SomeTrait:
@fieldwise_init
struct SomeStruct(SomeTrait):
def required_method(self, x: Int):
print("hello traits", x)
Then, here's a function that uses the trait as an argument type (instead of the struct type):
def fun_with_traits[T: SomeTrait](x: T):
x.required_method(42)
def use_trait_function():
var thing = SomeStruct()
fun_with_traits(thing)
You'll see traits used in a lot of APIs provided by Mojo's standard library. For
example, Mojo's collection types like List
and Dict can store any type that conforms
to the Movable trait (Dict keys must
also conform to KeyElement).
You can specify the type when you create a collection:
var my_list = List[Float64]()
Without traits, the x argument in fun_with_traits() would have to declare a
specific type that implements required_method(), such as SomeStruct (but
then the function would accept only that type). With traits, the function can
accept any type for x as long as it conforms to (it "implements") SomeTrait.
Thus, fun_with_traits() is a "parameterized function" because it accepts a
generalized type instead of a specific type.
For more details, see the page about traits.
Parameterization​
In Mojo, a parameter is a compile-time variable that becomes a runtime constant, and you declare it in square brackets on a function or struct. Parameters allow for compile-time metaprogramming, which means you can generate or modify code at compile time.
Many other languages use "parameter" and "argument" interchangeably, so be aware that when we say things like "parameter" and "parameterized function," we're talking about these compile-time parameters. In contrast, a function "argument" is a runtime value that you declare in parentheses.
Parameterization is a complex topic that the Metaprogramming section covers in much more detail, but we want to break the ice just a little bit here. To get you started, let's look at a parameterized function:
def repeat[count: Int](msg: String):
# evaluate the following for loop at compile time
comptime for i in range(count):
print(msg)
This function has one parameter of type Int and
one argument of type String. To call the function, you need to specify both
the parameter and the argument:
def call_repeat():
repeat[3]("Hello")
# Prints "Hello" 3 times
By specifying count as a parameter, the Mojo compiler can optimize the
function because this value can't change at runtime. And the comptime keyword
in the code tells the compiler to evaluate the for loop at compile time, not
runtime.
The compiler effectively generates a unique version of the repeat() function
that repeats the message only 3 times. This makes the code more performant
because there's less to compute at runtime.
Similarly, you can define a struct with parameters, which effectively allows you to define variants of that type at compile time, depending on the parameter values.
For more detail on parameters, see the section on Metaprogramming.
Python integration​
Mojo supports the ability to import Python modules as-is, so you can leverage existing Python code right away.
For example, here's how you can import and use NumPy:
from std.python import Python
def main() raises:
var np = Python.import_module("numpy")
var ar = np.arange(15).reshape(3, 5)
print(ar)
print(ar.shape)
You must have the Python module (such as numpy) installed in the environment
where you're using Mojo.
For more details, see the page on Python integration.
Next steps​
Hopefully this page has given you enough information to start experimenting with Mojo, but this is only touching the surface of what's available in Mojo.
If you're in the mood to read more, continue through each page of this Mojo Manual—the next page from here is Functions.
Otherwise, here are some other resources to check out:
-
See Get started with Mojo for a hands-on tutorial that gets you up and running with Mojo.
-
If you want to experiment with some code, clone our GitHub repo to try our code examples:
git clone https://github.com/modular/modular.gitcd modular/mojo/examples -
To see all the available Mojo APIs, check out the Mojo standard library reference.