Mojo aims to be a super-set of Python by supporting the Python syntax and adding in new keywords for more performant and safe code. Mojo is a compiler that produces extremely high-performance executable binary files. It offers interop with existing Python libraries and a limited set of Python types
Mojo cannot actually compile most existing Python code directly yet because of limited standard library support and incomplete language support (no list-comprehensions for instance.) These are on the roadmap to get added. However, Mojo can cooperate with a host Python interpreter and work with Python objects.
The Mojo Python interop capability even as it stands is especially interesting for the AI and machine learning communities. They heavily use Python already and want to keep their libraries and home-grown Python applications, but also want to make high-performance extensions without resorting to C++. Personally I think the combination of Rust + PyO3 is a good option but I would say Mojo looks much more approachable to many Python developers.
Mojo has one other big thing going for it over Rust: It targets MLIR. MLIR is a ddifferent backend than Rust targets (llvm) and it’s designed to utilize hardware (including better use of standard CPUs) particularly effective for machine learning workloads, but probably generally superior to existing backends. For this reason alone I’m very interested in following Mojo. It’s quite early to adopt it for most general purpose work.
Like Rust and other modern language ecosystems, the mojo
program is much more than a compiler. It can run, build, package, document, test, and format your projects. It can also update itself, similar to “rustup”. There doesn’t appear to be support for installing from a package repository yet but we’re very early in the project. The “modular” command will update Mojo itself and it lookslike it will get used to manage external packages.
Python and More
You can write Mojo using your typical Python style (dynamic typing) or with type hinting. Or, you can use the more strict Mojo super set for best performance: Mojo functions with fn
which impose strict type checking and memory safe behavior. They also allow for generating much faster executables.
The super-set part of the Mojo language looks similar to Nim, but a very streamlined version with simpler, better checked memory management. It’s a nice language if you can stay away from pointers, which it appears you mostly can. It’s biggest advantage is the high performance of the executables the compiler produces. The language makes design decisions for extra speedy, simple code. There’s special support for SIMD for example.
Sample strict Mojo:
from utils.vector import InlinedFixedVector
from utils.list import VariadicList
struct Customer :
var name: String
var rewards_points: Int
fn __init__(inout self, cust_name: String, points: Int):
self.name = cust_name
self.rewards_points = points
fn format(self) -> String:
return 'Customer: ' + self.name + ' : ' + self.rewards_points
fn main():
print("Hello world!")
let s: Customer = Customer("George Washington", 25)
print(s.format())
var v = DynamicVector[Int](25)
v.push_back(3)
v.push_back(999515)
print("Vector has ", v.size, " elements.")
var z = InlinedFixedVector[5, Customer](10)
# This doesn't work yet with non primitive types
# You could use pointers, Pointer[Customer].alloc()
#z.append(s)
Mojo adds struct
, and will eventually support class
types also. You can use var
and let
for variable declarations to describe variable mutability.
Mojo supports Python style def
functions with dynamic typing, or a strict fn
function type that enforces static type checking. Function calling conventions are the same as my hobby language: Immutable references are the default, (borrowed) but functions can declare an argument as variable instead. You don’t need a special sigil at the call site to indicate mutability. This makes optimizations easier for the compiler.
Mojo has a borrow checker to make sure you don’t use mutable references in two places at once. This aspect appears substantially simpler for the programmer compared to Rust. I don’t know how much safety is traded away in the process.
Supporting Existing Local Python Modules and Third-Party Libraries
Mojo also offers interfacing to the existing Python ecosystem: You can import any Python module. However, to achieve this, you have to have an existing Python interpreter available. Mojo itself can’t compile a lot of Python code, since it doesn’t have standard library support (maybe it’s coming?) For instance Mojo doesn’t have the Complex
numeric type.
I learned this when trying to run a naive Mandelbrot set generator I wrote for benchmarking. I was able to get the program to run by importing my code as a Python module – it worked – but all Mojo did was offload the execution to my system’s Python. (You build your executable, and when you run it, if it requires Python library support it dynamically calls on your system’s Python.) So, the test program ran exactly as slowly as it did when running directly under Python.
I could have rewritten the program to eliminate use of the Complex type and implement one of my own, or just write the complex number arithmetic by hand. According to what I read, it seems the spirit of Mojo is to take that kind of direct, naive code and produce really fast programs. I haven’t tried it yet but I found this Benchmarking Mojo vs Python on Mandelbrot where the programmer did exactly that, and basically did my benchmark but with a much more thorough treatment than I had planned.
To use Python libraries you do
from python import Python
from python.object import Object
def main():
let np = Python.import_module("numpy")
array = np.array([1, 2, 3])
print(array)
# Here 'array' is a Python object (a sort of type in Mojo)
# it isn't converted to a Mojo array type.
var stuff: PythonObject = ["car","desk","apple","toaster"]
var python_dict= Python.dict()
python_dict["my_stuff"] = stuff
Then you can call np
functions and interact with Mojo by converting Python data types to Mojo types. Tuples, lists, strings, booleans, ints and floats are currently supported. I’m sure dicts are coming soon.
It sounds like the plan is to embed a Python interpreter into the Mojo-produced binaries so you can deploy programs more easily. I don’t know about how Mojo development works with regard to Python packaging and environments. I think for now you still need an external environment wherever you deploy to.
Mojo will import modules from the lib
and bin
Python directories; you can set up virtual environments with “venv” and use them from Mojo.
How Would I Use This?
I’m not sure if people will take a Python-first approach, or instead write mainly Mojo programs and just import certain Python modules as needed. The Python first way would look like a bunch of imported Python modules into a file with some performance critical Mojo functions.
If I had to use Mojo today I’d start out with a Mojo first approach and write with full type annotations. To me it could be an attractive platform for prototyping data processing problems faster than with Rust.