print("Hello Mojo!")
Hello Mojo!
Mojo is a powerful programming language that’s primarily designed for high-performance systems programming, so it has a lot in common with other systems languages like Rust and C++. Yet, Mojo is also designed to become a superset of Python, so a lot of language features and concepts you might know from Python translate nicely to Mojo.
For example, if you’re in a REPL environment or Jupyter notebook (like this document), you can run top-level code just like Python:
You don’t normally see that with other systems programming languages.
Mojo preserves Python’s dynamic features and language syntax, and it even allows you to import and run code from Python packages. However, it’s important to know that Mojo is an entirely new language, not just a new implementation of Python with syntax sugar. Mojo takes the Python language to a whole new level, with systems programming features, strong type-checking, memory safety, next-generation compiler technologies, and more. Yet, it’s still designed to be a simple language that’s useful for general-purpose programming.
This page provides a gentle introduction to the Mojo language, and requires only a little programming experience. So let’s get started!
If you’re an experienced systems programmer and want a deep dive into the language, check out the Mojo programming manual.
First and foremost, Mojo is a compiled language and a lot of its performance and memory-safety features are derived from that fact. Mojo code can be ahead-of-time (AOT) or just-in-time (JIT) compiled.
Like other compiled languages, Mojo programs (.mojo
or .🔥
files) require a main()
function as the entry point to the program. For example:
If you know Python, you might have expected the function name to be def main()
instead of fn main()
. Both actually work in Mojo, but using fn
behaves a bit differently, as we’ll discuss below.
Of course, if you’re building a Mojo module (an API library), not a Mojo program, then your file doesn’t need a main()
function (because it will be imported by other programs that do have one).
Note: When you’re writing code in a .mojo
/.🔥
file, you can’t run top-level code as shown on this page—all code in a Mojo program or module must be encased in a function or struct. However, top-level code does work in a REPL or Jupyter notebook (such as the notebook for this page).
Now let’s explain the code in this main()
function.
This is simple: Mojo supports (or will support) all of Python’s syntax and semantics. If you’re not familiar with Python syntax, there are a ton of great resources online that can teach you.
For example, like Python, Mojo uses line breaks and indentation to define code blocks (not curly braces), and Mojo supports all of Python’s control-flow syntax such as if
conditions and for
loops.
However, Mojo is still a work in progress, so there are some things from Python that aren’t implemented in Mojo yet (see the Mojo roadmap). All the missing Python features will arrive in time, but Mojo already includes many features and capabilities beyond what’s available in Python.
As such, the following sections will focus on some of the language features that are unique to Mojo (compared to Python).
Mojo functions can be declared with either fn
(shown above) or def
(as in Python). The fn
declaration enforces strongly-typed and memory-safe behaviors, while def
provides Python-style dynamic behaviors.
Both fn
and def
functions have their value, and it’s important that you learn them both. However, for the purposes of this introduction, we’re going to focus on fn
functions only. For much more detail about both, see the programming manual.
In the following sections, you’ll learn how fn
functions enforce strongly-typed and memory-safe behaviors in your code.
You can declare variables (such as x
in the above main()
function) with var
to create a mutable value, or with let
to create an immutable value.
If you change var
to let
in the main()
function above and run it, you’ll get a compiler error like this:
error: Expression [15]:7:5: expression must be mutable for in-place operator destination
x += 1
^
That’s because let
makes the value immutable, so you can’t increment it.
And if you delete var
completely, you’ll get an error because fn
functions require explicit variable declarations (unlike Python-style def
functions).
Finally, notice that the x
variable has an explicit Int
type specification. Declaring the type is not required for variables in fn
, but it is desirable sometimes. If you omit it, Mojo infers the type, as shown here:
Although types aren’t required for variables declared in the function body, they are required for arguments and return values for an fn
function.
For example, here’s how to declare Int
as the type for function arguments and the return value:
You can also specify argument default values (also known as optional arguments), and pass values with keyword argument names. For example:
fn pow(base: Int, exp: Int = 2) -> Int:
return base ** exp
# Uses default value for `exp`
z = pow(3)
print(z)
# Uses keyword argument names (with order reversed)
z = pow(exp=3, base=2)
print(z)
9
8
Note: Mojo currently includes only partial support for keyword arguments, so some features such as keyword-only arguments and variadic keyword arguments (e.g. **kwargs
) are not supported yet.
Mojo supports full value semantics and enforces memory safety with a robust value ownership model (similar to the Rust borrow checker). So the following is a quick introduction to you can share references to values through function arguments.
Notice that, above, add()
doesn’t modify x
or y
, it only reads the values. In fact, as written, the function cannot modify them because fn
arguments are immutable references, by default.
In terms of argument conventions, this is called “borrowing,” and although it’s the default for fn
functions, you can make it explicit with the borrowed
declaration like this (this behaves exactly the same as the add()
above):
If you want the arguments to be mutable, you need to declare the argument convention as inout
. This means that changes made to the arguments inside the function are visible outside the function.
For example, this function is able to modify the original variables:
fn add_inout(inout x: Int, inout y: Int) -> Int:
x += 1
y += 1
return x + y
var a = 1
var b = 2
c = add_inout(a, b)
print(a)
print(b)
print(c)
2
3
5
Another option is to declare the argument as owned
, which provides the function full ownership of the value (it’s mutable and guaranteed unique). This way, the function can modify the value and not worry about affecting variables outside the function. For example:
fn set_fire(owned text: String) -> String:
text += "🔥"
return text
fn mojo():
let a: String = "mojo"
let b = set_fire(a)
print(a)
print(b)
mojo()
mojo
mojo🔥
In this case, Mojo makes a copy of a
and passes it as the text
argument. The original a
string is still alive and well.
However, if you want to give the function ownership of the value and do not want to make a copy (which can be an expensive operation for some types), then you can add the ^
“transfer” operator when you pass a
to the function. The transfer operator effectively destroys the local variable name—any attempt to call upon it later causes a compiler error.
Try it above by changing the call to set_fire()
to look like this:
You’ll now get an error because the transfer operator effectively destroys the a
variable, so when the following print()
function tries to use a
, that variable isn’t initialized anymore.
If you delete print(a)
, then it works fine.
These argument conventions are designed to provide systems programmers with total control for memory optimizations while ensuring safe access and timely deallocations—the Mojo compiler ensures that no two variables have mutable access to the same value at the same time, and the lifetime of each value is well-defined to strictly prevent any memory errors such as “use-after-free” and “double-free.”
Note: Currently, Mojo always makes a copy when a function returns a value.
You can build high-level abstractions for types (or “objects”) in a struct
. A struct
in Mojo is similar to a class
in Python: they both support methods, fields, operator overloading, decorators for metaprogramming, etc. However, Mojo structs are completely static—they are bound at compile-time, so they do not allow dynamic dispatch or any runtime changes to the structure. (Mojo will also support classes in the future.)
For example, here’s a basic struct:
And here’s how you can use it:
If you’re familiar with Python, then the __init__()
method and the self
argument should be familiar to you. If you’re not familiar with Python, then notice that, when we call dump()
, we don’t actually pass a value for the self
argument. The value for self
is automatically provided with the current instance of the struct (it’s used similar to the this
name used in some other languages to refer to the current instance of the object/type).
For much more detail about structs and other special methods like __init__()
(also known as “dunder” methods), see the programming manual.
Although Mojo is still a work in progress and is not a full superset of Python yet, we’ve built a mechanism to import Python modules as-is, so you can leverage existing Python code right away. Under the hood, this mechanism uses the CPython interpreter to run Python code, and thus it works seamlessly with all Python modules today.
For example, here’s how you can import and use NumPy (you must have Python numpy
installed):
from python import Python
let np = Python.import_module("numpy")
ar = np.arange(15).reshape(3, 5)
print(ar)
print(ar.shape)
[[ 0 1 2 3 4]
[ 5 6 7 8 9]
[10 11 12 13 14]]
(3, 5)
Note: Mojo is not a feature-complete superset of Python yet. So, you can’t always copy Python code and run it in Mojo. For more details on our plans, please refer to the Mojo roadmap and sharp edges.
Caution: When you install Mojo, the installer searches your system for a version of Python to use with Mojo, and adds the path to the modular.cfg
config file. If you change your Python version or switch virtual environments, Mojo will then be looking at the wrong Python library, which can cause problems such as errors when you import Python packages (Mojo says only An error occurred in Python
—this is a separate known issue). The current solution is to override Mojo’s path to the Python library, using the MOJO_PYTHON_LIBRARY
environment variable. For instructions on how to find and set this path, see this related issue.
We hope this page covered enough of the basics to get you started. It’s intentionally brief, so if you want more detail about any of the topics touched upon here, check out the Mojo programming manual.
If you want to package your code as a library, read about Mojo modules and packages.
If you want to explore some Mojo code, check out our code examples on GitHub.
To see all the available Mojo APIs, check out the Mojo standard library reference.
Note: The Mojo SDK is still in early development. Some things are still rough, but you can expect constant changes and improvements to both the language and tools. Please see the known issues and report any other issues on GitHub.