Mojo🔥 changelog
This is a list of changes to the Mojo language, standard library, and tools.
To check your current version, run mojo --version
. To update the version of
Mojo for your project with the magic
package manager, follow the instructions
in Update a package to update the max
package.
v24.5 (2024-09-13)
✨ Highlights
Here's a brief summary of some of the major changes in this release, with more detailed information in the following sections:
-
Mojo now supports Python 3.12 interoperability.
-
The set of automatically imported entities (types, aliases, functions) into users' Mojo programs has been dramatically reduced. This can break existing user code as users will need to explicitly import what they're using for cases previously automatically included before.
-
print()
now requires that its arguments conform to theFormattable
trait. This enables efficient stream-based writing by default, avoiding unnecessary intermediate String heap allocations. -
The new builtin
input()
function prints an optional prompt and reads a line from standard input, in the same way as Python. -
Mojo now allows implicit definitions of variables within a
fn
in the same way that has been allowed in adef
. Thevar
keyword is still allowed, but is now optional. -
Mojo now diagnoses "argument exclusivity" violations due to aliasing references. Mojo requires references (including implicit references due to
borrowed
/inout
arguments) to be uniquely referenced (non-aliased) if mutable. This is a warning in the 24.5 release, but will be upgraded to an error in subsequent releases. -
Mojo now supports "conditional conformances" where some methods on a struct have additional trait requirements that the struct itself doesn't.
-
DTypePointer
,LegacyPointer
, andPointer
have been removed. UseUnsafePointer
instead. Functions that previously took aDTypePointer
now take an equivalentUnsafePointer
. For more information on using pointers, see Unsafe pointers in the Mojo Manual. -
There are many new standard library APIs, with new features for strings, collections, and interacting with the filesystem and environment. Changes are listed in the standard library section.
-
The VS Code extension now supports a vendored MAX SDK for VS Code, which is automatically downloaded by the extension and it's used for all Mojo features, including the Mojo Language Server, the Mojo debugger, the Mojo formatter, and more.
-
mojo test
now uses the Mojo compiler for running unit tests. This will resolve compilation issues that sometimes appeared, and will also improve overall test execution times.
Language changes
-
Mojo now allows implicit definitions of variables within a
fn
in the same way that has been allowed in adef
. Thevar
keyword is still allowed and still denotes the declaration of a new variable with a scope (in bothdef
andfn
). Relaxing this makesfn
anddef
more similar, but they still differ in other important ways. -
Mojo now diagnoses "argument exclusivity" violations due to aliasing references. Mojo requires references (including implicit references due to
borrowed
/inout
arguments) to be uniquely referenced (non-aliased) if mutable. This is important for code safety, because it allows the compiler (and readers of code) to understand where and when a value is mutated. It is also useful for performance optimization because it allows the compiler to know that accesses through immutable references cannot change behind the scenes. Here is an invalid example:fn take_two_strings(a: String, inout b: String):
# Mojo knows 'a' and 'b' cannot be the same string.
b += a
fn invalid_access():
var my_string = String()
# warning: passing `my_string` inout is invalid since it is also passed
# borrowed.
take_two_strings(my_string, my_string)fn take_two_strings(a: String, inout b: String):
# Mojo knows 'a' and 'b' cannot be the same string.
b += a
fn invalid_access():
var my_string = String()
# warning: passing `my_string` inout is invalid since it is also passed
# borrowed.
take_two_strings(my_string, my_string)This is similar to Swift exclusivity checking and the Rust language sometimes known as "aliasing xor mutability". That said, the Mojo implementation details are somewhat different because lifetimes are embedded in types.
This is a warning in the 24.5 release, but will be upgraded to an error in subsequent releases.
-
Mojo now supports "conditional conformances" where some methods on a struct have additional trait requirements that the struct itself doesn't. This is expressed through an explicitly declared
self
type:struct GenericThing[Type: AnyType]: # Works with anything
# Sugar for 'fn normal_method[Type: AnyType](self: GenericThing[Type]):'
fn normal_method(self): ...
# Just redeclare the requirements with more specific types:
fn needs_move[Type: Movable](self: GenericThing[Type], owned val: Type):
var tmp = val^ # Ok to move 'val' since it is Movable
...
fn usage_example():
var a = GenericThing[Int]()
a.normal_method() # Ok, Int conforms to AnyType
a.needs_move(42) # Ok, Int is movable
var b = GenericThing[NonMovable]()
b.normal_method() # Ok, NonMovable conforms to AnyType
# error: argument type 'NonMovable' does not conform to trait 'Movable'
b.needs_move(NonMovable())struct GenericThing[Type: AnyType]: # Works with anything
# Sugar for 'fn normal_method[Type: AnyType](self: GenericThing[Type]):'
fn normal_method(self): ...
# Just redeclare the requirements with more specific types:
fn needs_move[Type: Movable](self: GenericThing[Type], owned val: Type):
var tmp = val^ # Ok to move 'val' since it is Movable
...
fn usage_example():
var a = GenericThing[Int]()
a.normal_method() # Ok, Int conforms to AnyType
a.needs_move(42) # Ok, Int is movable
var b = GenericThing[NonMovable]()
b.normal_method() # Ok, NonMovable conforms to AnyType
# error: argument type 'NonMovable' does not conform to trait 'Movable'
b.needs_move(NonMovable())Conditional conformance works with dunder methods and other things as well.
-
As a specific form of "conditional conformances", initializers in a struct may indicate specific parameter bindings to use in the type of their
self
argument. For example:@value
struct MyStruct[size: Int]:
fn __init__(inout self: MyStruct[0]): pass
fn __init__(inout self: MyStruct[1], a: Int): pass
fn __init__(inout self: MyStruct[2], a: Int, b: Int): pass
def test(x: Int):
a = MyStruct() # Infers size=0 from 'self' type.
b = MyStruct(x) # Infers size=1 from 'self' type.
c = MyStruct(x, x) # Infers size=2 from 'self' type.@value
struct MyStruct[size: Int]:
fn __init__(inout self: MyStruct[0]): pass
fn __init__(inout self: MyStruct[1], a: Int): pass
fn __init__(inout self: MyStruct[2], a: Int, b: Int): pass
def test(x: Int):
a = MyStruct() # Infers size=0 from 'self' type.
b = MyStruct(x) # Infers size=1 from 'self' type.
c = MyStruct(x, x) # Infers size=2 from 'self' type. -
Mojo now supports named result bindings. Named result bindings are useful for directly emplacing function results into the output slot of a function. This feature provides more flexibility and guarantees around emplacing the result of a function compared to "guaranteed" named return value optimization (NRVO). If a
@register_passable
result is bound to a name, the result value is made accessible as a mutable reference.fn efficiently_return_string(b: Bool) -> String as output:
if b:
output = "emplaced!"
mutate(output)
return
return "regular return"fn efficiently_return_string(b: Bool) -> String as output:
if b:
output = "emplaced!"
mutate(output)
return
return "regular return"If we used a temporary for
output
instead, we would need to move into the result slot, which wouldn't work if the result type was non-movable.In a function with a named result,
return
may be used with no operand to signal an exit from the function, or it can be used normally to specify the return value of the function. The compiler will error if the result is not initialized on all normal exit paths from the function. -
__setitem__()
now works with variadic argument lists such as:struct YourType:
fn __setitem__(inout self, *indices: Int, val: Int): ...struct YourType:
fn __setitem__(inout self, *indices: Int, val: Int): ...The Mojo compiler now always passes the "new value" being set using the last keyword argument of the
__setitem__()
, e.g. turningyourType[1, 2] = 3
intoyourType.__setitem__(1, 2, val=3)
. This fixes Issue #248. -
Mojo context managers used in regions of code that may raise no longer need to define a "conditional" exit function in the form of
fn __exit__(self, e: Error) -> Bool
. This function allows the context manager to conditionally intercept and handle the error and allow the function to continue executing. This is useful for some applications, but in many cases the conditional exit would delegate to the unconditional exit functionfn __exit__(self)
.Concretely, this enables defining
with
regions that unconditionally propagate inner errors, allowing code like:def might_raise() -> Int:
...
def foo() -> Int:
with ContextMgr():
return might_raise()
# no longer complains about missing return
def bar():
var x: Int
with ContextMgr():
x = might_raise()
print(x) # no longer complains about 'x' being uninitializeddef might_raise() -> Int:
...
def foo() -> Int:
with ContextMgr():
return might_raise()
# no longer complains about missing return
def bar():
var x: Int
with ContextMgr():
x = might_raise()
print(x) # no longer complains about 'x' being uninitialized -
async
functions now support memory-only results (likeString
,List
, etc.) andraises
. Accordingly, bothCoroutine
andRaisingCoroutine
have been changed to acceptAnyType
instead ofAnyTrivialRegType
. This means the result types ofasync
functions do not need to beMovable
.async fn raise_or_string(c: Bool) raises -> String:
if c:
raise "whoops!"
return "hello world!"async fn raise_or_string(c: Bool) raises -> String:
if c:
raise "whoops!"
return "hello world!"Note that
async
functions do not yet support indirect calls,ref
results, and constructors. -
The
Reference
type (and many iterators) now use infer-only parameters to represent the mutability of their lifetime, simplifying the interface. -
The environment variable
MOJO_PYTHON
can be pointed to an executable to pin Mojo to a specific version:export MOJO_PYTHON="/usr/bin/python3.11"
export MOJO_PYTHON="/usr/bin/python3.11"
Or a virtual environment to always have access to those Python modules:
export MOJO_PYTHON="~/venv/bin/python"
export MOJO_PYTHON="~/venv/bin/python"
MOJO_PYTHON_LIBRARY
still exists for environments with a dynamiclibpython
but no Python executable. -
The pointer aliasing semantics of Mojo have changed. Initially, Mojo adopted a C-like set of semantics around pointer aliasing and derivation. However, the C semantics bring a lot of history and baggage that are not needed in Mojo and which complicate compiler optimizations. The language overall provides a stronger set of invariants around pointer aliasing with lifetimes and exclusive mutable references to values, etc.
It is now forbidden to convert a non-pointer-typed value derived from a Mojo-allocated pointer, such as an integer address, to a pointer-typed value. "Derived" means there is overlap in the bits of the non-pointer-typed value with the original pointer value. Accordingly, the
UnsafePointer
constructor that took anaddress
keyword argument has been removed.It is still possible to make this conversion in certain cases where it is absolutely necessary, such as interoperating with other languages like Python. In this case, the compiler makes two assumptions: any pointer derived from a non-pointer-typed value does not alias any Mojo-derived pointer and that any external function calls have arbitrary memory effects.
-
await
on a coroutine now consumes it. This strengthens the invariant that coroutines can be awaited only once.
Standard library changes
-
builtin
package:-
The set of automatically imported entities (types, aliases, functions) into users' Mojo programs has been dramatically reduced. Before, with the way the
builtin
module was handled, all of the entities in the following modules would be automatically included:memory
,sys
,os
,utils
,python
,bit
,random
,math
,builtin
,collections
Now, only the explicitly enumerated entities in
prelude/__init__.mojo
are the ones automatically imported into users' Mojo programs. This will break a lot of user code as users will need to explicitly import what they're using for cases previously commonly included before (such asOptional
,Variant
, and functions such asabort()
,alignof()
,bitcast()
,bitwidthof()
,external_call()
,simdwidthof()
, andsizeof()
). -
Some types from the
builtin
module have been moved to different modules for clarity which is made possible now that we have aprelude
module that can re-export symbols from modules other thanbuiltin
.In particular, the
builtin.string
module has been moved tocollections.string
.
-
-
Input and output:
-
Added the builtin
input()
function, which behaves the same as Python. (PR #3392)name = input("Enter your name: ")
print("Hello, " + name + "!")name = input("Enter your name: ")
print("Hello, " + name + "!")If the user enters "Mojo" it returns "Hello, Mojo!"
There is a known issue when running the
input()
function with JIT compilation (see issue #3479). -
print()
now requires that its arguments conform to theFormattable
trait. This enables efficient stream-based writing by default, avoiding unnecessary intermediate String heap allocations.Previously,
print()
required types conform toStringable
. This meant that to execute a call likeprint(a, b, c)
, at least three separate String heap allocations were down, to hold the formatted values ofa
,b
, andc
respectively. The total number of allocations could be much higher if, for example,a.__str__()
was implemented to concatenate together the fields ofa
, like in the following example:struct Point(Stringable):
var x: Float64
var y: Float64
fn __str__(self) -> String:
# Performs 3 allocations: 1 each for str(..) of each of the fields,
# and then the final returned `String` allocation.
return "(" + str(self.x) + ", " + str(self.y) + ")"struct Point(Stringable):
var x: Float64
var y: Float64
fn __str__(self) -> String:
# Performs 3 allocations: 1 each for str(..) of each of the fields,
# and then the final returned `String` allocation.
return "(" + str(self.x) + ", " + str(self.y) + ")"A type like the one above can transition to additionally implementing
Formattable
with the following changes:struct Point(Stringable, Formattable):
var x: Float64
var y: Float64
fn __str__(self) -> String:
return String.format_sequence(self)
fn format_to(self, inout writer: Formatter):
writer.write("(", self.x, ", ", self.y, ")")struct Point(Stringable, Formattable):
var x: Float64
var y: Float64
fn __str__(self) -> String:
return String.format_sequence(self)
fn format_to(self, inout writer: Formatter):
writer.write("(", self.x, ", ", self.y, ")")In the example above,
String.format_sequence()
is used to construct aString
from a type that implementsFormattable
. This pattern of implementing a type'sStringable
implementation in terms of itsFormattable
implementation minimizes boilerplate and duplicated code, while retaining backwards compatibility with the requirements of the commonly usedstr()
function. -
debug_assert()
now also requires that itsmessage
argument conform toFormattable
. -
Added
TemporaryDirectory
in moduletempfile
. (PR 2743) -
Added
NamedTemporaryFile
in moduletempfile
. (PR 2762)
-
-
String
and friends:-
The
builtin.string
module has been moved tocollections.string
. -
Added the
String.format()
method. (PR #2771)Supports automatic and manual indexing of
*args
.Examples:
print(
String("{1} Welcome to {0} {1}").format("mojo", "🔥")
)
# 🔥 Wecome to mojo 🔥print(
String("{1} Welcome to {0} {1}").format("mojo", "🔥")
)
# 🔥 Wecome to mojo 🔥print(String("{} {} {}").format(True, 1.125, 2))
#True 1.125 2print(String("{} {} {}").format(True, 1.125, 2))
#True 1.125 2 -
String.format()
now supports conversion flags!s
and!r
, allowing forstr()
andrepr()
conversions within format strings. (PR #3279)Example:
String("{} {!r}").format("Mojo", "Mojo")
# "Mojo 'Mojo'"
String("{0!s} {0!r}").format("Mojo")
# "Mojo 'Mojo'"String("{} {!r}").format("Mojo", "Mojo")
# "Mojo 'Mojo'"
String("{0!s} {0!r}").format("Mojo")
# "Mojo 'Mojo'" -
The
String
class now hasrjust()
,ljust()
, andcenter()
methods to return a justified string based on width and fillchar. (PR #3278) -
The
atol()
function now correctly supports leading underscores, (e.g.atol("0x_ff", 0)
), when the appropriate base is specified or inferred (base 0). non-base-10 integer literals as per Python's Integer Literals. (PR #3180) -
Added the
unsafe_cstr_ptr()
method toString
andStringLiteral
, which returns anUnsafePointer[c_char]
for convenient interoperability with C APIs. -
Added the
byte_length()
method toString
,StringSlice
, andStringLiteral
and deprecated their private_byte_length()
methods. Added a warning to theString.__len__()
method that it will return the length in Unicode codepoints in the future andStringSlice.__len__()
now does return the Unicode codepoints length. (PR #2960) -
Added a new
StaticString
type alias. This can be used in place ofStringLiteral
for runtime string arguments. -
Added a
StringSlice
initializer that accepts aStringLiteral
. -
The
StringRef
constructors fromDTypePointer.int8
have been changed to take aUnsafePointer[c_char]
, reflecting their use for compatibility with C APIs. -
Continued the transition to
UnsafePointer
and unsigned byte type for strings:-
String.unsafe_ptr()
now returns anUnsafePointer[UInt8]
(wasUnsafePointer[Int8]
) -
StringLiteral.unsafe_ptr()
now returns anUnsafePointer[UInt8]
(wasUnsafePointer[Int8]
)
-
-
-
UnsafePointer
and other reference type changes:-
DTypePointer
,LegacyPointer
, andPointer
have been removed. UseUnsafePointer
instead. For more information on using pointers, see Unsafe pointers in the Mojo Manual.Functions that previously took a
DTypePointer
now take an equivalentUnsafePointer
. A quick rule for conversion fromDTypePointer
toUnsafePointer
is:DTypePointer[type] -> UnsafePointer[Scalar[type]]
DTypePointer[type] -> UnsafePointer[Scalar[type]]
There could be places that you have code of the form:
fn f(ptr: DTypePointer):
fn f(ptr: DTypePointer):
which is equivalent to
DTypePointer[*_]
. In this case you would have to add an infer-onlytype
parameter to the function:fn f[type: DType, //](ptr: UnsafePointer[Scalar[type]]):
fn f[type: DType, //](ptr: UnsafePointer[Scalar[type]]):
because we can’t have an unbound parameter inside the struct.
There could also be places where you use
DTypePointer[Scalar[DType.invalid/index]]
, and it would be natural to change these toUnsafePointer[NoneType/Int]
. But since these are not anUnsafePointer
that stores aScalar
, you might have torebind/bitcast
to appropriate types. -
The
DTypePointer
load()
andstore()
methods have been moved toUnsafePointer
. -
UnsafePointer
now supportsstrided_load()
,strided_store()
,gather()
, andscatter()
when the underlying type isScalar[DType]
. -
The global functions for working with
UnsafePointer
have transitioned to being methods through the use of conditional conformances:destroy_pointee(p)
=>p.destroy_pointee()
move_from_pointee(p)
=>p.take_pointee()
initialize_pointee_move(p, value)
=>p.init_pointee_move(value)
initialize_pointee_copy(p, value)
=>p.init_pointee_copy(value)
move_pointee(src=p1, dst=p2)
=>p.move_pointee_into(p2)
-
The
UnsafePointer.offset()
method is deprecated and will be removed in a future release. Use pointer arithmetic instead.new_ptr = ptr.offset(1)
new_ptr = ptr.offset(1)
Becomes:
new_ptr = ptr + 1
new_ptr = ptr + 1
-
UnsafePointer
now has analignment
parameter to specify the static alignment of the pointer. Consequently,UnsafePointer.alloc()
no longer takes in an alignment parameter, and the alignment should be specified in the type.UnsafePointer[type].alloc[alignment](x) # now becomes
UnsafePointer[type, alignment].alloc(x)UnsafePointer[type].alloc[alignment](x) # now becomes
UnsafePointer[type, alignment].alloc(x) -
UnsafePointer
has a newexclusive: Bool = False
parameter. Setting this parameter to true tells the compiler that the user knows this pointer and all those derived from it have exclusive access to the underlying memory allocation. The compiler is not guaranteed to do anything with this information. -
It is no longer possible to cast (implicitly or explicitly) from
Reference
toUnsafePointer
. Instead ofUnsafePointer(someRef)
please use theUnsafePointer.address_of(someRef[])
which makes the code explicit that theUnsafePointer
gets the address of what the reference points to.
-
-
Python interoperability changes:
-
Mojo now supports Python 3.12 interoperability.
-
Creating a nested
PythonObject
from a list or tuple of Python objects is possible now:var np = Python.import_module("numpy")
var a = np.array([1, 2, 3])
var b = np.array([4, 5, 6])
var arrays = PythonObject([a, b])
assert_equal(len(arrays), 2)var np = Python.import_module("numpy")
var a = np.array([1, 2, 3])
var b = np.array([4, 5, 6])
var arrays = PythonObject([a, b])
assert_equal(len(arrays), 2)Also allowing more convenient call syntax:
var stacked = np.hstack((a, b))
assert_equal(str(stacked), "[1 2 3 4 5 6]")var stacked = np.hstack((a, b))
assert_equal(str(stacked), "[1 2 3 4 5 6]")(PR #3264)
-
Accessing local Python modules with
Python.add_to_path(".")
is no longer required. It now behaves the same as Python. You can access modules in the same folder as the target file:-
mojo run /tmp/main.mojo
can access/tmp/mymodule.py
-
mojo build main.mojo -o ~/myexe && ~/myexe
can access~/mymodule.py
-
-
-
Collections:
-
List
values are now equality comparable with==
and!=
when their element type is equality comparable. (PR #3195) -
Optional
values are now equality comparable with==
and!=
when their element type is equality comparable. -
Added a new
Counter
dictionary-like type, matching most of the features of the Python one. (PR #2910) -
Dict
now implementssetdefault()
, which gets a value from the dictionary by key, or sets it to a default if it doesn't exist. (PR #2803) -
Dict
now supportspopitem()
, which removes and returns the last item in theDict
. (PR #2701) -
Added a
Dict.__init__()
overload to specify initial capacity. (PR #3171)The capacity has to be a power of two and greater than or equal to 8.
It allows for faster initialization by skipping incremental growth steps.
Example:
var dictionary = Dict[Int,Int](power_of_two_initial_capacity = 1024)
# Insert (2/3 of 1024) entriesvar dictionary = Dict[Int,Int](power_of_two_initial_capacity = 1024)
# Insert (2/3 of 1024) entries -
ListLiteral
now supports__contains__()
. (PR #3251)
-
-
Filesystem and environment utilities:
-
Path.home()
has been added to return a path of the user's home directory. -
os.path.expanduser()
andpathlib.Path.exapanduser()
have been added to allow expanding a prefixed~
in aString
orPath
with the user's home path:import os
print(os.path.expanduser("~/.modular"))
# /Users/username/.modular
print(os.path.expanduser("~root/folder"))
# /var/root/folder (on macos)
# /root/folder (on linux)import os
print(os.path.expanduser("~/.modular"))
# /Users/username/.modular
print(os.path.expanduser("~root/folder"))
# /var/root/folder (on macos)
# /root/folder (on linux) -
os.path.split()
has been added for splitting a path intohead, tail
:import os
head, tail = os.path.split("/this/is/head/tail")
print("head:", head)
print("tail:", tail)
# head: /this/is/head
# tail: tailimport os
head, tail = os.path.split("/this/is/head/tail")
print("head:", head)
print("tail:", tail)
# head: /this/is/head
# tail: tail -
os.makedirs()
andos.removedirs()
have been added for creating and removing nested directories:import os
path = os.path.join("dir1", "dir2", "dir3")
os.path.makedirs(path, exist_ok=True)
os.path.removedirs(path)import os
path = os.path.join("dir1", "dir2", "dir3")
os.path.makedirs(path, exist_ok=True)
os.path.removedirs(path) -
The
pwd
module has been added for accessing user information in/etc/passwd
on POSIX systems. This follows the same logic as Python:import pwd
import os
current_user = pwd.getpwuid(os.getuid())
print(current_user)
# pwd.struct_passwd(pw_name='jack', pw_passwd='********', pw_uid=501,
# pw_gid=20, pw_gecos='Jack Clayton', pw_dir='/Users/jack',
# pw_shell='/bin/zsh')
print(current_user.pw_uid)
# 501
root = pwd.getpwnam("root")
print(root)
# pwd.struct_passwd(pw_name='root', pw_passwd='*', pw_uid=0, pw_gid=0,
# pw_gecos='System Administrator', pw_dir='/var/root', pw_shell='/bin/zsh')import pwd
import os
current_user = pwd.getpwuid(os.getuid())
print(current_user)
# pwd.struct_passwd(pw_name='jack', pw_passwd='********', pw_uid=501,
# pw_gid=20, pw_gecos='Jack Clayton', pw_dir='/Users/jack',
# pw_shell='/bin/zsh')
print(current_user.pw_uid)
# 501
root = pwd.getpwnam("root")
print(root)
# pwd.struct_passwd(pw_name='root', pw_passwd='*', pw_uid=0, pw_gid=0,
# pw_gecos='System Administrator', pw_dir='/var/root', pw_shell='/bin/zsh')
-
-
Other new traits and related features:
-
Added the
ExplicitlyCopyable
trait to mark types that can be copied explicitly, but which might not be implicitly copyable.This supports work to transition the standard library collection types away from implicit copyability, which can lead to unintended expensive copies.
-
Added the
Identifiable
trait, used to describe types that implement the__is__()
and__isnot__()
trait methods. (PR #2807) -
Types conforming to
Boolable
(that is, those implementing__bool__()
) no longer implicitly convert toBool
. A newImplicitlyBoolable
trait is introduced for types where this behavior is desired.
-
-
Miscellaneous:
-
NoneType
is now a normal standard library type, and not an alias for a raw MLIR type.Function signatures written as
fn() -> NoneType
should transition to being written asfn() -> None
. -
Mojo now has a
UInt
type for modeling unsigned (scalar) integers with a platform-dependent width.UInt
implements most arithmetic operations that make sense for integers, with the notable exception of__neg__()
. Builtin functions such asmin()
/max()
, as well asmath
functions likeceildiv()
,align_down()
, andalign_up()
are also implemented forUInt
. -
Now that we have a
UInt
type, use this to represent the return type of a hash. In general, hashes should be an unsigned integer, and can also lead to improved performance in certain cases. -
Added the
c_char
type alias insys.ffi
. -
sort()
now supports astable
parameter. It can be called bysort[cmp_fn, stable=True](list)
sort[cmp_fn, stable=True](list)
The algorithm requires auxiliary memory. If extra memory allocation fails, the program crashs.
-
sort()
no longer takesLegacyPointer
since that type is now removed. -
Added the
oct()
builtin function for formatting an integer in octal. (PR #2914) -
Added the
assert_is()
andassert_is_not()
test functions to thetesting
module. -
The
math
package now includes thepi
,e
, andtau
constants (Closes Issue #2135). -
The
ulp
function fromnumerics
has been moved to themath
module. -
bit
module now supportsbit_reverse()
,byte_swap()
, andpop_count()
for theInt
type. (PR #3150) -
A few
bit
functions have been renamed for clarity:-
countl_zero()
->count_leading_zeros()
-
countr_zero()
->count_trailing_zeros()
-
-
Slice
now usesOptionalReg[Int]
forstart
andend
and implements a constructor which accepts optional values.Slice._has_end()
has also been removed since a Slice with no end is now represented by an emptySlice.end
option. (PR #2495)var s = Slice(1, None, 2)
print(s.start.value()) # must retrieve the value from the optionalvar s = Slice(1, None, 2)
print(s.start.value()) # must retrieve the value from the optional -
The
rank
argument foralgorithm.elementwise()
is no longer required and is only inferred. -
The
time.now()
function has been deprecated. Please usetime.perf_counter()
ortime.perf_counter_ns
instead. -
SIMD
construction fromBool
has been restricted toDType.bool
data type.
-
Tooling changes
-
mojo test
new features and changes:-
mojo test
now uses the Mojo compiler for running unit tests. This will resolve compilation issues that sometimes appeared, and will also improve overall test times, since we will only compile unit tests once before executing all of them.These changes do not apply to doctests, due to their different semantics.
-
The
mojo test
command now accepts a--filter
option that will narrow the set of tests collected and executed. The filter string is a POSIX extended regular expression. -
The
mojo test
command now supports using the same compilation options asmojo build
. -
You can now debug unit tests using
mojo test
by passing the--debug
flag. Most debug flags are supported; runmojo test --help
for a full listing.Debugging doctests is not currently supported.
-
-
Mojo debugger new features and changes:
-
The
mojo debug --rpc
command has been renamed tomojo debug --vscode
, which is now able to manage multiple VS Code windows. -
The Mojo debugger now supports a
break-on-raise
command that indicated the debugger to stop at anyraise
statements. A similar features has been added to the debugger on VS Code. -
The Mojo debugger now hides the artificial function arguments
__result__
and__error__
created by the compiler for Mojo code.
-
-
VS Code support changes:
-
The VS Code extension now supports a vendored MAX SDK for VS Code, which is automatically downloaded by the extension and it's used for all Mojo features, including the Mojo Language Server, the Mojo debugger, the Mojo formatter, and more.
-
A proxy has been added to the Mojo Language Server on VS Code that handles crashes more gracefully.
-
-
The Mojo Language Server no longer sets
.
as a commit character for auto-completion.
❌ Removed
-
Support for the legacy
fn __init__(...) -> Self:
form has been removed from the compiler, please switch to usingfn __init__(inout self, ...):
instead. -
The builtin
tensor
module has been removed. Identical functionality is available inmax.tensor
, but it is generally recommended to use structs from thebuffer
module when possible instead. -
Removed
String.unsafe_uint8_ptr()
.String.unsafe_ptr()
now returns the same thing. -
Removed
StringLiteral.unsafe_uint8_ptr()
andStringLiteral.as_uint8_ptr()
. -
Removed
SIMD.splat(value: Scalar[type])
. Use the constructor forSIMD
instead. -
Removed the
SIMD.{add,mul,sub}_with_overflow()
methods. -
Removed the
SIMD.min()
andSIMD.max()
methods. Identical functionality is available using the builtinmin()
andmax()
functions. -
Removed the Mojo Language Server warnings for unused function arguments.
-
Run Mojo File in Dedicated Terminal
action has been removed, and the actionRun Mojo File
will always open a dedicated terminal for each mojo file to guarantee a correct environment.
🛠️ Fixed
-
Fixed a crash in the Mojo Language Server when importing the current file.
-
Fixed crash when specifying variadic keyword arguments without a type expression in
def
functions, e.g.:def foo(**kwargs): ... # now works
def foo(**kwargs): ... # now works
-
Mojo now prints
ref
arguments and results in generated documentation correctly. -
#1734 - Calling
__copyinit__
on self causes crash. -
#3142 - [QoI] Confusing
__setitem__
method is failing with a "must be mutable" error. -
#248 - [Feature] Enable
__setitem__
to take variadic arguments -
#3065 - Fix incorrect behavior of
SIMD.__int__
on unsigned types -
#3045 - Disable implicit SIMD conversion routes through
Bool
-
#3126 - [BUG] List doesn't work at compile time.
-
#3237 - [BUG] Difference between
__getitem__
and[.]
operator. -
#3336 - Fix outdated references to
let
in REPL documentation. -
The VS Code extension no longer caches the information of the selected MAX SDK, which was causing issues upon changes in the SDK.
-
The Mojo debugger now stops showing spurious warnings when parsing closures.
Special thanks
Special thanks to our community contributors: @jjvraw, @artemiogr97, @martinvuyk, @jayzhan211, @bgreni, @mzaks, @msaelices, @rd4com, @jiex-liu, @kszucs, @thatstoasty
v24.4 (2024-06-07)
✨ Highlights
Big themes for this release:
-
Improvements to the performance and ease-of-use for
def
functions. -
Continued unification of standard library APIs around the
UnsafePointer
type. -
Many quality-of-life improvements for the standard library collection types.
-
Significant performance improvements when inserting into a
Dict
. Performance on this metric is still not where we'd like it to be, but it is much improved. -
A new
@parameter for
mechanism for expressing compile-time loops, which replaces the earlier (and less reliable)@unroll
decorator. -
New Mojo Manual pages on Control flow, Testing and using unsafe pointers.
Language changes
-
Mojo has changed how
def
function arguments are processed. Previously, by default, arguments to adef
were treated according to theowned
convention, which makes a copy of the value, enabling that value to be mutable in the callee.This could lead to major performance issues because of the proliferation of unnecessary copies. It also required you to declare non-copyable types as
borrowed
explicitly. Now Mojo takes a different approach:def
functions take arguments asborrowed
by default (consistent withfn
functions) but will make a local copy of the value only if the argument is mutated in the body of the function.This improves consistency, performance, and ease of use.
-
Implicit variable definitions in a
def
function are more flexible: you can now implicitly declare variables as the result of a tuple return, usinga,b,c = foo()
. For example:def return_two(i: Int) -> (Int, Int):
return i, i+1
a, b = return_two(5)def return_two(i: Int) -> (Int, Int):
return i, i+1
a, b = return_two(5)Implicit variable declarations can also now shadow global immutable symbols (such as module names and built-ins) without getting a compiler error. For example:
slice = foo()
slice = foo()
-
Mojo functions can return an auto-dereferenced reference to storage with a new
ref
keyword in the result type specifier. For example:@value
struct Pair:
var first: Int
var second: Int
fn get_first_ref(inout self) -> ref [self] Int:
return self.first
fn show_mutation():
var somePair = Pair(5, 6)
somePair.get_first_ref() = 1@value
struct Pair:
var first: Int
var second: Int
fn get_first_ref(inout self) -> ref [self] Int:
return self.first
fn show_mutation():
var somePair = Pair(5, 6)
somePair.get_first_ref() = 1This approach provides a general way to return an "automatically dereferenced" reference of a given type. Notably, this eliminates the need for
__refitem__()
to exist.__refitem__()
has thus been removed and replaced with__getitem__()
that returns a reference. -
Mojo added support for infer-only parameters. Infer-only parameters must appear at the beginning of the parameter list and cannot be explicitly specified by the user. They are declared to the left of a
//
marker, much like positional-only parameters. This allows programmers to define functions with dependent parameters to be called without the caller specifying all the necessary parameters. For example:fn parameter_simd[dt: DType, //, value: Scalar[dt]]():
print(value)
fn call_it():
parameter_simd[Int32(42)]()fn parameter_simd[dt: DType, //, value: Scalar[dt]]():
print(value)
fn call_it():
parameter_simd[Int32(42)]()In the above example,
Int32(42)
is passed directly intovalue
, the first parameter that isn't infer-only.dt
is inferred from the parameter itself to beDType.int32
.This also works with structs. For example:
struct ScalarContainer[dt: DType, //, value: Scalar[dt]]:
pass
fn foo(x: ScalarContainer[Int32(0)]): # 'dt' is inferred as `DType.int32`
passstruct ScalarContainer[dt: DType, //, value: Scalar[dt]]:
pass
fn foo(x: ScalarContainer[Int32(0)]): # 'dt' is inferred as `DType.int32`
passThis should make working with dependent parameters more ergonomic. See Infer-only parameters in the Mojo Manual.
-
Mojo now allows functions overloaded on parameters to be resolved when forming references to, but not calling, those functions. For example, the following now works:
fn overloaded_parameters[value: Int32]():
pass
fn overloaded_parameters[value: Float32]():
pass
fn form_reference():
alias ref = overloaded_parameters[Int32()] # works!fn overloaded_parameters[value: Int32]():
pass
fn overloaded_parameters[value: Float32]():
pass
fn form_reference():
alias ref = overloaded_parameters[Int32()] # works! -
Mojo now supports adding a
@deprecated
decorator on structs, functions, traits, aliases, and global variables. The decorator marks the attached declaration as deprecated and causes a warning to be emitted when the deprecated declaration is referenced in user code. The decorator requires a deprecation message, specified as a string literal.@deprecated("Foo is deprecated, use Bar instead")
struct Foo:
pass
fn outdated_api(x: Foo): # warning: Foo is deprecated, use Bar instead
pass
@deprecated("use another function!")
fn bar():
pass
fn techdebt():
bar() # warning: use another function!@deprecated("Foo is deprecated, use Bar instead")
struct Foo:
pass
fn outdated_api(x: Foo): # warning: Foo is deprecated, use Bar instead
pass
@deprecated("use another function!")
fn bar():
pass
fn techdebt():
bar() # warning: use another function! -
Mojo has introduced
@parameter for
, a new feature for compile-time programming.@parameter for
defines a for loop where the sequence and the induction values in the sequence must be parameter values. For example:fn parameter_for[max: Int]():
@parameter
for i in range(max)
@parameter
if i == 10:
print("found 10!")fn parameter_for[max: Int]():
@parameter
for i in range(max)
@parameter
if i == 10:
print("found 10!")Currently,
@parameter for
requires the sequence's__iter__()
method to return a_StridedRangeIterator
, meaning the induction variables must beInt
. The intention is to lift these restrictions in the future. -
The
is_mutable
parameter ofReference
andAnyLifetime
is now aBool
, not a low-level__mlir_type.i1
value.This improves the ergonomics of spelling out a
Reference
type explicitly. -
Mojo will now link to a Python dynamic library based on the Python on top of your search path:
PATH
. This enables you to activate a virtual environment likeconda
and have access to Python modules installed in that environment without settingMOJO_PYTHON_LIBRARY
. Previously Mojo would find alibpython
dynamic library on installation and put the path in.modular/modular.cfg
, which could result in version conflicts if you activated a virtual environment of a different Python version. -
AnyRegType
has been renamed toAnyTrivialRegType
and Mojo now forbids binding non-trivial register-passable types toAnyTrivialRegType
. This closes a major safety hole in the language. Please useAnyType
for generic code going forward. -
The
let
keyword has been completely removed from the language. We previously removedlet
declarations but still provided an error message to users. Now, it is completely gone from the grammar.
Standard library changes
-
New traits and related features:
-
Added built-in
repr()
function andRepresentable
trait. (PR #2361) -
Added the
Indexer
trait to denote types that implement the__index__()
method which allows these types to be accepted in common__getitem__()
and__setitem__()
implementations, as well as allow a new built-inindex()
function to be called on them. Most standard library containers can now be indexed by any type that implementsIndexer
. For example:@value
struct AlwaysZero(Indexer):
fn __index__(self) -> Int:
return 0
struct MyList:
var data: List[Int]
fn __init__(inout self):
self.data = List[Int](1, 2, 3, 4)
fn __getitem__[T: Indexer](self, idx: T) -> Int:
return self.data[index(idx)]
print(MyList()[AlwaysZero()]) # prints `1`@value
struct AlwaysZero(Indexer):
fn __index__(self) -> Int:
return 0
struct MyList:
var data: List[Int]
fn __init__(inout self):
self.data = List[Int](1, 2, 3, 4)
fn __getitem__[T: Indexer](self, idx: T) -> Int:
return self.data[index(idx)]
print(MyList()[AlwaysZero()]) # prints `1`Types conforming to the
Indexer
trait are implicitly convertible to Int. This means you can write generic APIs that takeInt
instead of making them take a generic type that conforms toIndexer
. For example:@value
struct AlwaysZero(Indexer):
fn __index__(self) -> Int:
return 0
@value
struct Incrementer:
fn __getitem__(self, idx: Int) -> Int:
return idx + 1
var a = Incrementer()
print(a[AlwaysZero()]) # works and prints 1@value
struct AlwaysZero(Indexer):
fn __index__(self) -> Int:
return 0
@value
struct Incrementer:
fn __getitem__(self, idx: Int) -> Int:
return idx + 1
var a = Incrementer()
print(a[AlwaysZero()]) # works and prints 1(PR #2685)
-
Added traits allowing user-defined types to be supported by various built-in and math functions.
Function Trait Required method abs()
Absable
__abs__()
pow()
Powable
__pow__()
round()
Roundable
__round__()
math.ceil
math.Ceilable
__ceil__()
math.ceildiv
math.CeilDivable
math.CeilDivableRaising
__ceildiv__()
math.floor
math.Floorable
__floor__()
math.trunc
Truncable
__trunc__()
Notes:
-
Conforming to the
Powable
trait also means that the type can be used with the power operator (**
). -
For
ceildiv()
, structs can conform to either theCeilDivable
trait orCeilDivableRaising
trait. -
Due to ongoing refactoring, the traits
Ceilable
,CeilDivable
,Floorable
, andTruncable
do not appear in the API reference. They should be imported from themath
module, except forTruncable
which is (temporarily) available as a built-in trait and does not need to be imported.
Example:
from math import sqrt
@value
struct Complex2(Absable, Roundable):
var re: Float64
var im: Float64
fn __abs__(self) -> Self:
return Self(sqrt(self.re * self.re + self.im * self.im), 0.0)
fn __round__(self) -> Self:
return Self(round(self.re, 0), round(self.im, 0))
fn __round__(self, ndigits: Int) -> Self:
return Self(round(self.re, ndigits), round(self.im, ndigits))from math import sqrt
@value
struct Complex2(Absable, Roundable):
var re: Float64
var im: Float64
fn __abs__(self) -> Self:
return Self(sqrt(self.re * self.re + self.im * self.im), 0.0)
fn __round__(self) -> Self:
return Self(round(self.re, 0), round(self.im, 0))
fn __round__(self, ndigits: Int) -> Self:
return Self(round(self.re, ndigits), round(self.im, ndigits)) -
-
-
Benchmarking:
- The
bencher
module as part of thebenchmark
package is now public and documented. This module provides types such asBencher
which provides the ability to execute aBenchmark
and allows for benchmarking configuration via theBenchmarkConfig
struct.
- The
-
String
and friends:-
Breaking. Implicit conversion to
String
is now removed for builtin classes/types. Usestr()
explicitly to convert toString
. -
Added
String.isspace()
method conformant with Python's universal separators. This replaces theisspace()
free function from thestring
module. (If you need the old function, it is temporarily available as_isspace()
. It now takes aUInt8
but is otherwise unchanged.) -
String.split()
now defaults to whitespace and has Pythonic behavior in that it removes all adjacent whitespace by default. -
String.strip()
,lstrip()
andrstrip()
can now remove custom characters other than whitespace. In addition, there are now several useful aliases for whitespace, ASCII lower/uppercase, and so on. (PR #2555) -
String
now has asplitlines()
method, which allows splitting strings at line boundaries. This method supports universal newlines and provides an option to retain or remove the line break characters. (PR #2810) -
InlinedString
has been renamed toInlineString
to be consistent with other types. -
StringRef
now implementsstrip()
, which can be used to remove leading and trailing whitespace. (PR #2683) -
StringRef
now implementsstartswith()
andendswith()
. (PR #2710) -
Added a new
StringSlice
type, to replace uses of the unsafeStringRef
type in standard library code.StringSlice
is a non-owning reference to encoded string data. UnlikeStringRef
, aStringSlice
is safely tied to the lifetime of the data it points to.- Added new
as_string_slice()
methods toString
andStringLiteral
. - Added
StringSlice
initializer from anUnsafePointer
and a length in bytes.
- Added new
-
Added a new
as_bytes_slice()
method toString
andStringLiteral
, which returns aSpan
of the bytes owned by the string. -
Continued transition to
UnsafePointer
and unsigned byte type for strings:- Renamed
String._as_ptr()
toString.unsafe_ptr()
, and changed return type toUnsafePointer
(wasDTypePointer
). - Renamed
StringLiteral.data()
toStringLiteral.unsafe_ptr()
, and changed return type toUnsafePointer
(wasDTypePointer
). InlineString.as_ptr()
has been renamed tounsafe_ptr()
and now returns anUnsafePointer[UInt8]
(wasDTypePointer[DType.int8]
).StringRef.data
is now anUnsafePointer
(wasDTypePointer
) andStringRef.unsafe_ptr()
now returns anUnsafePointer[UInt8]
(wasDTypePointer[DType.int8]
).
- Renamed
-
-
Other built-ins:
-
The
Slice.__len__()
function has been removed andSlice
no longer conforms to theSized
trait. This clarifies the ambiguity of the semantics: the length of a slice always depends on the length of the object being sliced. Users that need the existing functionality can use theSlice.unsafe_indices()
method. This makes it explicit that this implementation does not check if the slice bounds are concrete or within any given object's length. -
Added a built-in
sort()
function for lists of elements that conform to theComparableCollectionElement
trait.(PR #2609) -
int()
can now take a string and a specified base to parse an integer from a string:int("ff", 16)
returns255
. Additionally, if a base of zero is specified, the string will be parsed as if it was an integer literal, with the base determined by whether the string contains the prefix"0x"
,"0o"
, or"0b"
. (PR #2273, fixes #2274) -
Added the
bin()
built-in function to convert integral types into their binary string representation. (PR #2603) -
Added the
atof()
built-in function, which can convert aString
to afloat64
. (PR #2649) -
You can now use the built-in
any()
andall()
functions to check for truthy elements in a collection. BecauseSIMD.__bool__()
is now constrained tosize=1
, You must explicitly use these to get the truthy value of a SIMD vector with more than one element. This avoids common bugs around implicit conversion ofSIMD
toBool
. (PR #2600)For example:
fn truthy_simd():
var vec = SIMD[DType.int32, 4](0, 1, 2, 3)
if any(vec):
print("any elements are truthy")
if all(vec):
print("all elements are truthy")fn truthy_simd():
var vec = SIMD[DType.int32, 4](0, 1, 2, 3)
if any(vec):
print("any elements are truthy")
if all(vec):
print("all elements are truthy") -
Tuple
now supports__contains__()
. (PR #2709) For example:var x = Tuple(1, 2, True)
if 1 in x:
print("x contains 1")var x = Tuple(1, 2, True)
if 1 in x:
print("x contains 1") -
ListLiteral
andTuple
now only require that element types beMovable
. Consequently,ListLiteral
andTuple
are themselves no longerCopyable
. -
Added new
ImmutableStaticLifetime
andMutableStaticLifetime
helpers.
-
-
UnsafePointer
and others:-
Added new
memcpy()
overload forUnsafePointer[Scalar[_]]
pointers. -
Removed the
get_null()
method fromUnsafePointer
and other pointer types. Please use the default constructor instead:UnsafePointer[T]()
. -
Many functions returning a pointer type have been unified to have a public API function of
unsafe_ptr()
. -
The
Tensor.data()
method has been renamed tounsafe_ptr()
. The return type is still aDTypePointer[T]
.
-
-
Collections:
-
List
now has anindex()
method that allows you to find the (first) location of an element in aList
ofEqualityComparable
types. For example:var my_list = List[Int](2, 3, 5, 7, 3)
print(my_list.index(3)) # prints 1var my_list = List[Int](2, 3, 5, 7, 3)
print(my_list.index(3)) # prints 1 -
List
can now be converted to aString
with a simplified syntax:var my_list = List[Int](2, 3)
print(my_list.__str__()) # prints [2, 3]var my_list = List[Int](2, 3)
print(my_list.__str__()) # prints [2, 3]Note that
List
doesn't conform to theStringable
trait yet so you cannot usestr(my_list)
yet. (PR #2673) -
List
has a simplified syntax to call thecount()
method:my_list.count(x)
. (PR #2675) -
List()
now supports__contains__()
, so you can now use lists with thein
operator:if x in my_list:
if x in my_list:
(PR #2667)
-
List
now has anunsafe_get()
to get the reference to an element without bounds check or wraparound for negative indices. Note that this method is unsafe. Use with caution. PR #2800) -
Added a
fromkeys()
method toDict
to return aDict
with the specified keys and values. (PR 2622) -
Dict
now supportsreversed()
for itsitems()
andvalues()
iterators. (PR #2340) -
Dict
now has a simplified conversion toString
withmy_dict.__str__()
. Note thatDict
does not conform to theStringable
trait sostr(my_dict)
is not possible yet. (PR #2674) -
Dict
now implementsget(key)
andget(key, default)
functions. (PR #2519) -
Added a temporary
__get_ref(key)
method toDict
, allowing you to get aReference
to a dictionary value. -
Added a new
InlineList
type, a stack-allocated list with a static maximum size. (PR 2587#) (PR #2703) -
Added a new
Span
type for taking slices of contiguous collections. (PR #2595)
-
-
os
module:-
The
os
module now provides functionality for adding and removing directories usingmkdir()
andrmdir()
. (PR #2430) -
Added the
os.path.getsize()
function, which gives the size in bytes of the file identified by the path. (PR 2626) -
Added
os.path.join()
function. (PR 2792) -
Added a new
tempfile
module, withgettempdir()
andmkdtemp()
functions. (PR 2742)
-
-
SIMD
type:-
Added
SIMD.shuffle()
withIndexList
mask. (PR #2315) -
SIMD.__bool__()
is constrained such that it only works whensize
is1
. For SIMD vectors with more than one element, useany()
orall()
. (PR #2502) -
The
SIMD.reduce_or()
andSIMD.reduce_and()
methods are now bitwise operations, and support integer types. (PR #2671) -
Added
SIMD.__repr__()
to get the verbose string representation ofSIMD
types. (PR #2728)
-
-
math
package:-
The
math.bit
module has been moved to a new top-levelbit
module. The following functions in this module have been renamed:ctlz
->countl_zero
cttz
->countr_zero
bit_length
->bit_width
ctpop
->pop_count
bswap
->byte_swap
bitreverse
->bit_reverse
-
The
math.rotate_bits_left()
andmath.rotate_bits_right()
functions have been moved to thebit
module. -
The
is_power_of_2()
function in themath
module is now calledis_power_of_two()
and located in thebit
module. -
The
abs()
,round()
,min()
,max()
,pow()
, anddivmod()
functions have moved frommath
tobuiltin
, so you no longer need to import these functions. -
The
math.tgamma()
function has been renamed tomath.gamma()
to conform with Python's naming. -
The implementation of the following functions have been moved from the
math
module to the newutils.numerics
module:isfinite()
,isinf()
,isnan()
,nan()
,nextafter()
, andulp()
. The functions continue to be exposed in themath
module. -
math.gcd()
now works on negative inputs, and like Python's implementation, accepts a variadic list of integers. New overloads for aList
orSpan
of integers are also added. (PR #2777)
-
-
Async and coroutines:
-
Coroutine
now requires a lifetime parameter. This parameter is set automatically by the parser when calling an async function. It contains the lifetimes of all the arguments and any lifetime accesses by the arguments. This ensures that argument captures by async functions keep the arguments alive as long as the coroutine is alive. -
Async function calls are no longer allowed to borrow non-trivial register-passable types. Because async functions capture their arguments but register-passable types don't have lifetimes (yet), Mojo is not able to correctly track the reference, making this unsafe. To cover this safety gap, Mojo has temporarily disallowed binding non-trivial register-passable types to borrowed arguments in async functions.
-
-
Miscellaneous:
-
Added an
InlineArray
type that works on memory-only types. Compare with the existingStaticTuple
type, which is conceptually an array type, but only works onAnyTrivialRegType
. (PR #2294) -
The
base64
package now includes encoding and decoding support for both the Base64 and Base16 encoding schemes. (PR #2364) (PR #2584) -
The
take()
function inVariant
andOptional
has been renamed tounsafe_take()
. -
The
get()
function inVariant
has been replaced by__getitem__()
. That is,v.get[T]()
should be replaced withv[T]
. -
Various functions in the
algorithm
module are now built-in functions. This includessort()
,swap()
, andpartition()
.swap()
andpartition()
will likely shuffle around as we're reworking our built-insort()
function and optimizing it.
-
-
infinity
andNaN
are now correctly handled intesting.assert_almost_equal()
and aninf
function has been added toutils/numerics.mojo
. (PR #2375)
Tooling changes
-
Invoking
mojo package my-package -o my-dir
on the command line, wheremy-package
is a Mojo package source directory, andmy-dir
is an existing directory, now outputs a Mojo package tomy-dir/my-package.mojopkg
. Previously, this had to be spelled out, as in-o my-dir/my-package.mojopkg
. -
The Mojo Language Server now reports a warning when a local variable is unused.
-
Several
mojo
subcommands now support a--diagnostic-format
option that changes the format with which errors, warnings, and other diagnostics are printed. By specifying--diagnostic-format json
on the command line, errors and other diagnostics will be output in a structured JSON Lines format that is easier for machines to parse.The full list of subcommands that support
--diagnostic-format
is as follows:mojo build
,mojo doc
,mojo run
,mojo package
, andmojo test
. Further, themojo test --json
option has been subsumed into this new option; for the same behavior, runmojo test --diagnostic-format json
.Note that the format of the JSON output may change; we don't currently guarantee its stability across releases of Mojo.
-
A new
--validate-doc-strings
option has been added tomojo
to emit errors on invalid doc strings instead of warnings. -
The
--warn-missing-doc-strings
flag formojo
has been renamed to--diagnose-missing-doc-strings
. -
A new decorator,
@doc_private
, was added that can be used to hide a declaration from being generated in the output ofmojo doc
. It also removes the requirement that the declaration has documentation (for example, when used with--diagnose-missing-doc-strings
). -
Debugger users can now set breakpoints on function calls in O0 builds even if the call has been inlined by the compiler.
-
The Mojo Language Server now supports renaming local variables.
Other changes
❌ Removed
-
The
@unroll
decorator has been deprecated and removed. The decorator was supposed to guarantee that a decorated loop would be unrolled, or else the compiler would error. In practice, this guarantee was eroded over time, as a compiler-based approach cannot be as robust as the Mojo parameter system. In addition, the@unroll
decorator did not make the loop induction variables parameter values, limiting its usefulness. Please see@parameter for
for a replacement! -
The method
object.print()
has been removed. Sinceobject
now conforms to theStringable
trait, you can useprint(my_object)
instead. -
The following functions have been removed from the math module:
clamp()
; use the newSIMD.clamp()
method instead.round_half_down()
andround_half_up()
; these can be trivially implemented using theceil()
andfloor()
functions.add()
,sub()
,mul()
,div()
,mod()
,greater()
,greater_equal()
,less()
,less_equal()
,equal()
,not_equal()
,logical_and()
,logical_xor()
, andlogical_not()
; Instead, users should rely directly on the corresponding operators (+
,-
,*
,/
,%
,>
,>=
,<
,<=
,==
,!=
,&
,^
, and~
).identity()
andreciprocal()
; users can implement these trivially.select()
; removed in favor of usingSIMD.select()
directly.is_even()
andis_odd()
; these can be trivially implemented using bitwise&
with1
.roundeven()
; the newSIMD.roundeven()
method now provides the identical functionality.div_ceil()
; use the newceildiv()
function.rotate_left()
androtate_right()
; the same functionality is available in the builtinSIMD.rotate_{left,right}()
methods forSIMD
types, and thebit.rotate_bits_{left,right})()
methods forInt
.- An overload of
math.pow()
taking an integer parameter exponent. align_down_residual()
; it can be trivially implemented usingalign_down()
.all_true()
,any_true()
, andnone_true()
; useSIMD.reduce_and()
andSIMD.reduce_or()
directly.reduce_bit_count()
; use the newSIMD.reduce_bit_count()
directly.rint()
andnearbyint()
; useround()
orSIMD.roundeven()
as appropriate.
-
The
EvaluationMethod
has been removed frommath.polynomial
and Estrin's method is no longer available. This method was limited to degree 10 or less, underutilized, and its performance unclear. In the future, this might be reintroduced with an improved implementation if needed, when better performance benchmarking infrastructure is available. The default behavior ofmath.polynomial.polynomial_evaluate()
is unchanged (Horner's method). -
The
math.bit.select()
andmath.bit.bit_and()
functions have been removed. The same functionality is available in the builtinSIMD.select
andSIMD.__and__()
methods, respectively. -
The
math.limit
module has been removed. The same functionality is available as follows:math.limit.inf()
: useutils.numerics.max_or_inf()
math.limit.neginf()
: useutils.numerics.min_or_neg_inf()
math.limit.max_finite()
: useutils.numerics.max_finite()
math.limit.min_finite()
: useutils.numerics.min_finite()
-
The
tensor.random
module has been removed. The same functionality is now accessible via theTensor.rand()
andTensor.randn()
static methods. -
The builtin
SIMD
struct no longer conforms toIndexer
; users must explicitly castScalar
values usingint
.
🛠️ Fixed
- #1837 Fix self-referential variant crashing the compiler.
- #2363 Fix LSP crashing on simple trait definitions.
- #1787 Fix error when using
//
onFloatLiteral
in alias expression. - Made several improvements to dictionary performance. Dicts with integer keys are most heavily affected, but large dicts and dicts with large values will also see large improvements.
- #2692 Fix
assert_raises
to include calling location.
Special thanks
Special thanks to our community contributors:
@rd4com, @toiletsandpaper, @helehex, @artemiogr97, @mikowals, @kernhanda, @lsh, @LJ-9801, @YichengDWu, @gabrieldemarmiesse, @fknfilewalker, @jayzhan211, @martinvuyk, @ChristopherLR, @mzaks, @bgreni, @Brian-M-J, @leandrolcampos
v24.3 (2024-05-02)
✨ Highlights
-
AnyPointer
was renamed toUnsafePointer
and is now Mojo's preferred unsafe pointer type. It has several enhancements, including:-
The element type can now be any type: it doesn't require
Movable
. -
Because of this, the
take_value()
,emplace_value()
, andmove_into()
methods have been changed to top-level functions and renamed. The new functions are: -
A new
destroy_pointee()
function runs the destructor on the pointee. -
UnsafePointer
can be initialized directly from aReference
withUnsafePointer(someRef)
and can convert to a reference withyourPointer[]
. Both infer element type and address space. Note that when you convert a pointer to a reference, there's no way for Mojo to track the lifetime of the original value. So the resulting reference is no safer than the original pointer.
-
-
All of the pointer types received some cleanup to make them more consistent, for example the
unsafe.bitcast()
global function is now a consistentbitcast()
method on the pointers, which can convert element type and address space. -
Improvements to variadic arguments support.
-
Heterogeneous variadic pack arguments now work reliably even with memory types, and have a more convenient API to use, as defined by the
VariadicPack
type. For example, a simplified version ofprint
can be implemented like this:fn print[T: Stringable, *Ts: Stringable](first: T, *rest: *Ts):
print_string(str(first))
@parameter
fn print_elt[T: Stringable](a: T):
print_string(" ")
print_string(a)
rest.each[print_elt]()fn print[T: Stringable, *Ts: Stringable](first: T, *rest: *Ts):
print_string(str(first))
@parameter
fn print_elt[T: Stringable](a: T):
print_string(" ")
print_string(a)
rest.each[print_elt]() -
Mojo now supports declaring functions that have both optional and variadic arguments, both positional and keyword-only. For example, this now works:
fn variadic_arg_after_default(
a: Int, b: Int = 3, *args: Int, c: Int, d: Int = 1, **kwargs: Int
): ...fn variadic_arg_after_default(
a: Int, b: Int = 3, *args: Int, c: Int, d: Int = 1, **kwargs: Int
): ...Positional variadic parameters also work in the presence of optional parameters. That is:
fn variadic_param_after_default[e: Int, f: Int = 2, *params: Int]():
passfn variadic_param_after_default[e: Int, f: Int = 2, *params: Int]():
passNote that variadic keyword parameters are not supported yet.
For more information, see Variadic arguments in the Mojo Manual.
-
-
The
mojo build
andmojo run
commands now support a-g
option. This shorter alias is equivalent to writing--debug-level full
. This option is also available in themojo debug
command, but is already the default. -
Many new standard library APIs have been filled in, including many community contributions. Changes are listed in the standard library section.
-
The Mojo Manual has a new page on Types.
Language changes
-
Certain dunder methods that take indices (
__getitem__()
,__setitem__()
, and__refitem__()
) or names (__getattr__()
and__setattr__()
) can now take the index or name as a parameter value instead of an argument value. This is enabled when you define one of these methods with no argument other thanself
(for a getter) orself
and the set value (for a setter).This enables types that can only be subscripted into with parameters, as well as things like the following example, which passes the attribute name as a parameter so that attribute names can be checked at compile time.
struct RGB:
fn __getattr__[name: StringLiteral](self) -> Int:
@parameter
if name == "r": return ...
elif name == "g": return ...
else:
constrained[name == "b", "can only access with r, g, or b members"]()
return ...
var rgb = RGB()
print(rgb.b) # Works
print(rgb.q) # Compile errorstruct RGB:
fn __getattr__[name: StringLiteral](self) -> Int:
@parameter
if name == "r": return ...
elif name == "g": return ...
else:
constrained[name == "b", "can only access with r, g, or b members"]()
return ...
var rgb = RGB()
print(rgb.b) # Works
print(rgb.q) # Compile error -
Mojo now allows users to capture the source location of code and call location of functions dynamically using the
__source_location()
and__call_location()
functions. For example:from builtin._location import __call_location
@always_inline
fn my_assert(cond: Bool, msg: String):
if not cond:
var call_loc = __call_location()
print("In", call_loc.file_name, "on line", str(call_loc.line) + ":", msg)
fn main():
my_assert(False, "always fails") # some_file.mojo, line 193from builtin._location import __call_location
@always_inline
fn my_assert(cond: Bool, msg: String):
if not cond:
var call_loc = __call_location()
print("In", call_loc.file_name, "on line", str(call_loc.line) + ":", msg)
fn main():
my_assert(False, "always fails") # some_file.mojo, line 193This prints "
In /path/to/some_file.mojo on line 193: always fails
". Note that__call_location()
only works in@always_inline
or@always_inline("nodebug")
functions. It gives incorrect results if placed in an@always_inline
function that's called from an@always_inline("nodebug")
function.This feature is still evolving and for the time being you need to explicitly import these APIs, as shown above. In the future, these will probably be built-in functions and not require an import statement.
Neither
__source_location()
nor__call_location()
work when called in a parameter context. For example:from builtin._location import __call_location
@always_inline
fn mystery_location() -> String:
var loc = __call_location()
return str(loc.file_name)
def main():
alias doesnt_work = mystery_location() # <unknown location in parameter context>from builtin._location import __call_location
@always_inline
fn mystery_location() -> String:
var loc = __call_location()
return str(loc.file_name)
def main():
alias doesnt_work = mystery_location() # <unknown location in parameter context>
Standard library changes
⭐️ New
-
List
has several new methods:-
pop(index)
for removing an element at a particular index. By default,List.pop()
removes the last element in the list. (@LJ-9801, fixes #2017) -
resize(new_size)
for resizing the list without the need to specify an additional value. (@mikowals, fixes #2133) -
insert(index, value)
for inserting a value at a specified index into theList
. (@whym1here, fixes #2134) -
A new constructor
List(ptr, size, capacity)
to to avoid needing to do a deep copy of an existing contiguous memory allocation when constructing a newList
. (@StandinKP, fixes #2170)
-
-
Dict
now has aupdate()
method to update keys/values from anotherDict
. (@gabrieldemarmiesse) -
Set
now has named methods for set operations:difference()
mapping to-
difference_update()
mapping to-=
intersection_update()
mapping to&=
update()
mapping to|=
-
Dict
,List
, andSet
all conform to theBoolable
trait. The collections evaluate toTrue
if they contain any elements,False
otherwise:def list_names(names: List[String]):
if names:
for name in names:
print(name[])
else:
print("No names to list.")def list_names(names: List[String]):
if names:
for name in names:
print(name[])
else:
print("No names to list.") -
Added
reversed()
function for creating reversed iterators. Several range types,List
, andDict
now support iterating in reverse.var numbers = List(1, 2, 3, 4, 5)
for number in reversed(numbers):
print(number)var numbers = List(1, 2, 3, 4, 5)
for number in reversed(numbers):
print(number)(@helehex and @jayzhan211, contributes towards #2325)
-
Optional
now implements__is__
and__isnot__
methods so that you can compare anOptional
withNone
. For example:var opt = Optional(1)
if opt is not None:
print(opt.value()[])var opt = Optional(1)
if opt is not None:
print(opt.value()[]) -
Tuple
now works with memory-only element types likeString
and allows you to directly index into it with a parameter expression. This means you can now simply usex = tup[1]
like Python instead ofx = tup.get[1, Int]()
. You can also assign into tuple elements now as well withtup[1] = x
.var tuple = ("Green", 9.3)
var name = tuple[0]
var value = tuple[1]var tuple = ("Green", 9.3)
var name = tuple[0]
var value = tuple[1]Note that because the subscript must be a parameter expression, you can't iterate through a
Tuple
using an ordinaryfor
loop. -
The
Reference
type has several changes, including:-
It has moved to the
memory.reference
module instead ofmemory.unsafe
. -
Reference
now has anunsafe_bitcast()
method, similar to the pointer types. -
Several unsafe methods were removed, including
offset()
,destroy_element_unsafe()
andemplace_ref_unsafe()
. This is becauseReference
is a safe type—useUnsafePointer
to do unsafe operations.
-
-
Bool
can now be implicitly converted from any type conforming to theBoolable
trait. This means that you no longer need to write code like this:@value
struct MyBoolable:
fn __bool__(self) -> Bool: ...
fn takes_boolable[T: Boolable](cond: T): ...
takes_boolable(MyBoolable())@value
struct MyBoolable:
fn __bool__(self) -> Bool: ...
fn takes_boolable[T: Boolable](cond: T): ...
takes_boolable(MyBoolable())Instead, you can simply write:
fn takes_bool(cond: Bool): ...
takes_bool(MyBoolable())fn takes_bool(cond: Bool): ...
takes_bool(MyBoolable())Note that calls to
takes_bool()
will perform the implicit conversion, so in some cases is it still better to explicitly declare a type parameter, e.g.:fn takes_two_boolables[T: Boolable](a: T, b: T):
# Short circuit means `b.__bool__()` might not be evaluated.
if a.__bool__() and b.__bool__():
...fn takes_two_boolables[T: Boolable](a: T, b: T):
# Short circuit means `b.__bool__()` might not be evaluated.
if a.__bool__() and b.__bool__():
... -
PythonObject
now conforms to theKeyElement
trait, meaning that it can be used as key type forDict
. This allows you to easily build and interact with Python dictionaries in Mojo:def main():
d = PythonObject(Dict[PythonObject, PythonObject]())
d["foo"] = 12
d[7] = "bar"
d["foo"] = [1, 2, "something else"]
print(d) # prints `{'foo': [1, 2, 'something else'], 7: 'bar'}`def main():
d = PythonObject(Dict[PythonObject, PythonObject]())
d["foo"] = 12
d[7] = "bar"
d["foo"] = [1, 2, "something else"]
print(d) # prints `{'foo': [1, 2, 'something else'], 7: 'bar'}` -
FileHandle.seek()
now has awhence
argument that defaults toos.SEEK_SET
to seek from the beginning of the file. You can now set toos.SEEK_CUR
to offset by the currentFileHandle
seek position:var f = open("/tmp/example.txt")
# Skip 32 bytes
f.seek(os.SEEK_CUR, 32)var f = open("/tmp/example.txt")
# Skip 32 bytes
f.seek(os.SEEK_CUR, 32)Or
os.SEEK_END
to offset from the end of file:# Start from 32 bytes before the end of the file
f.seek(os.SEEK_END, -32)# Start from 32 bytes before the end of the file
f.seek(os.SEEK_END, -32) -
FileHandle.read()
can now read straight into aDTypePointer
:var file = open("/tmp/example.txt", "r")
# Allocate and load 8 elements
var ptr = DTypePointer[DType.float32].alloc(8)
var bytes = file.read(ptr, 8)
print("bytes read", bytes)
print(ptr.load[width=8]())var file = open("/tmp/example.txt", "r")
# Allocate and load 8 elements
var ptr = DTypePointer[DType.float32].alloc(8)
var bytes = file.read(ptr, 8)
print("bytes read", bytes)
print(ptr.load[width=8]()) -
The
sys
module now contains anexit()
function that would exit a Mojo program with the specified error code.from sys import exit
exit(0)from sys import exit
exit(0) -
The constructors for
Tensor
have been changed to be more consistent. As a result, constructors take the shape as the first argument (instead of the second) when constructing a tensor with pointer data.If you pass a single scalar value to the
Tensor
constructor, it now broadcasts the value to all elements in the tensor. For example,Tensor[DType.float32](TensorShape(2,2), 0)
constructs a2x2
tensor initialized with all zeros. This provides an easy way to fill in the data of a tensor. -
String
now hasremoveprefix()
andremovesuffix()
methods. (@gabrieldemarmiesse) -
The
ord
andchr
functions have been improved to accept any Unicode character. (@mzaks, contributes towards #1616) -
atol()
now handles whitespace. Theatol()
function is used internally byString.__int__()
, soint(String( " 10 "))
now returns10
instead of raising an error. (@artemiogr97) -
SIMD
now implements the__rmod__()
method. (@bgreni, fixes #1482) -
bool(None)
is now implemented. (@zhoujingya) -
The
DTypePointer
type now implementsgather()
for gathering aSIMD
vector from offsets of a current pointer. Similarly, support forscatter()
was added to scatter aSIMD
vector into offsets of the current pointer. (@leandrolcampos) -
The
len()
function now handles arange()
specified with a negative end value, so that things likelen(range(-1))
work correctly. (@soraros) -
debug_assert()
now prints its location (filename, line, and column where it was called) in its error message. Similarly, theassert
helpers in thetesting
module now include location information in their messages. -
The
testing.assert_equal[SIMD]()
function now raises if any of the elements mismatch in the twoSIMD
arguments being compared. (@gabrieldemarmiesse) -
The
testing.assert_almost_equal()
andmath.isclose()
functions now have anequal_nan
flag. When set toTrue
, then NaNs are considered equal. -
The
object
type now supports the division, modulo, and left and right shift operators, including the in-place and reverse variants. (@LJ-9801, fixes #2224) -
Added checked arithmetic operations for
SIMD
integers.SIMD
integer types (including the sized integer scalars likeInt64
) can now perform checked additions, subtractions, and multiplications using the following new methods:add_with_overflow()
sub_with_overflow()
mul_with_overflow()
Checked arithmetic allows the caller to determine if an operation exceeded the numeric limits of the type. For example:
var simd = SIMD[DType.int8, 4](7, 11, 13, 17)
var product: SIMD[DType.int8, 4]
var overflow: SIMD[DType.bool, 4]
(product, overflow) = simd.mul_with_overflow(simd)
for i in range(len(product)):
if overflow[i]:
print("<overflow>")
else:
print(product[i])var simd = SIMD[DType.int8, 4](7, 11, 13, 17)
var product: SIMD[DType.int8, 4]
var overflow: SIMD[DType.bool, 4]
(product, overflow) = simd.mul_with_overflow(simd)
for i in range(len(product)):
if overflow[i]:
print("<overflow>")
else:
print(product[i])(@lsh)
-
Added
os.remove()
andos.unlink()
for deleting files. (@artemiogr97, fixes #2306)
🦋 Changed
-
The
parallel_memcpy()
function has moved from thebuffer
package to thealgorithm
package. Please update your imports accordingly. -
Optional.value()
now returns a reference instead of a copy of the contained value.To perform a copy manually, dereference the result:
var result = Optional(123)
var value = result.value()[]var result = Optional(123)
var value = result.value()[] -
Per the accepted community proposal, Standardize the representation of byte sequence as a sequence of unsigned 8-bit integers, began transition to using
UInt8
by changing the data pointer ofError
toDTypePointer[DType.uint8]
. (@gabrieldemarmiesse, contributes towards #2317) -
Continued transition to
UnsafePointer
from the legacyPointer
type in various standard library APIs and internals. (@gabrieldemarmiesse)
Tooling changes
-
The behavior of
mojo build
when invoked without an output-o
argument has changed slightly:mojo build ./test-dir/program.mojo
now outputs an executable to the path./program
, whereas before it would output to the path./test-dir/program
. -
The
mojo package
command no longer supports the-D
flag. All compilation environment flags should be provided at the point of package use (e.g.mojo run
ormojo build
). -
The REPL no longer allows type level variable declarations to be uninitialized, e.g. it will reject
var s: String
. This is because it does not do proper lifetime tracking (yet!) across cells, and so such code would lead to a crash. You can work around this by initializing to a dummy value and overwriting later. This limitation only applies to top level variables, variables in functions work as they always have.
Other changes
Low-level language changes
-
A low-level
__get_mvalue_as_litref(x)
builtin was added to give access to the underlying memory representation as a!lit.ref
value without checking initialization status of the underlying value. This is useful in very low-level logic but isn't designed for general usability and will likely change in the future. -
Properties can now be specified on inline MLIR ops:
_ = __mlir_op.`kgen.source_loc`[
_type = (
__mlir_type.index, __mlir_type.index, __mlir_type.`!kgen.string`
),
_properties = __mlir_attr.`{inlineCount = 1 : i64}`,
]()_ = __mlir_op.`kgen.source_loc`[
_type = (
__mlir_type.index, __mlir_type.index, __mlir_type.`!kgen.string`
),
_properties = __mlir_attr.`{inlineCount = 1 : i64}`,
]()As the example shows above, the protected
_properties
attribute can be passed during op construction, with an MLIRDictionaryAttr
value.
❌ Removed
-
Support for "register only" variadic packs has been removed. Instead of
AnyRegType
, please upgrade your code toAnyType
in examples like this:fn your_function[*Types: AnyRegType](*args: *Ts): ...
fn your_function[*Types: AnyRegType](*args: *Ts): ...
This move gives you access to a nicer API and has the benefit of being memory safe and correct for non-trivial types. If you need specific APIs on the types, please use the correct trait instead of
AnyType
. -
List.pop_back()
has been removed. UseList.pop()
instead which defaults to popping the last element in the list. -
SIMD.to_int(value)
has been removed. Useint(value)
instead. -
The
__get_lvalue_as_address(x)
magic function has been removed. To get a reference to a value useReference(x)
and if you need an unsafe pointer, you can useUnsafePointer.address_of(x)
.
🛠️ Fixed
-
#516 and #1817 and many others, e.g. "Can't create a function that returns two strings."
-
#1178 (os/kern) failure (5).
-
#1609 alias with
DynamicVector[Tuple[Int]]
fails. -
#1987 Defining
main
in a Mojo package is an error, for now. This is not intended to work yet, erroring for now will help to prevent accidental undefined behavior. -
#1215 and #1949 The Mojo LSP server no longer cuts off hover previews for functions with functional arguments, parameters, or results.
-
#1901 Fixed Mojo LSP and documentation generation handling of inout arguments.
-
#1913 -
0__
no longer crashes the Mojo parser. -
#1924 JIT debugging on Mac has been fixed.
-
#1941 Mojo variadic arguments don't work with non-trivial register-only types.
-
#1963
a!=0
is now parsed and formatted correctly bymojo format
. -
#1676 Fix a crash related to
@value
decorator and structs with empty body. -
#1917 Fix a crash after syntax error during tuple creation.
-
#2006 The Mojo LSP now properly supports signature types with named arguments and parameters.
-
#2007 and #1997 The Mojo LSP no longer crashes on certain types of closures.
-
#1675 Ensure
@value
decorator fails gracefully after duplicate field error.
v24.2.1 (2024-04-11)
This release doesn't include any changes to Mojo.
v24.2 (2024-03-28)
🔥 Legendary
-
The Mojo standard library is now open source! Check out the README for everything you need to get started.
-
Structs and other nominal types are now allowed to implicitly conform to traits. A struct implicitly conforms to a trait if it implements all the requirements for the trait. For example, any struct that implements the
__str__()
method implicitly conforms toStringable
, and is usable with thestr()
built-in function.@value
struct Foo:
fn __str__(self) -> String:
return "foo!"
fn main():
print(str(Foo())) # prints 'foo!'@value
struct Foo:
fn __str__(self) -> String:
return "foo!"
fn main():
print(str(Foo())) # prints 'foo!'We still strongly encourage you to explicitly list the traits a struct conforms to when possible:
@value
struct Foo(Stringable): ...@value
struct Foo(Stringable): ...Not only is this useful for documentation and for communicating intentions, but in the future, explicit conformance will be useful for features like default methods and extensions.
-
Mojo's Python interoperability now supports passing keyword arguments to Python functions:
from python import Python
def main():
plt = Python.import_module("matplotlib.pyplot")
plt.plot((5, 10), (10, 15), color="red")
plt.show()from python import Python
def main():
plt = Python.import_module("matplotlib.pyplot")
plt.plot((5, 10), (10, 15), color="red")
plt.show()
Language changes
⭐️ New
-
Mojo now has support for variadic keyword arguments, often referred to as
**kwargs
. This means you can now declare and call functions like this:fn print_nicely(**kwargs: Int) raises:
for key in kwargs.keys():
print(key[], "=", kwargs[key[]])
# prints:
# `a = 7`
# `y = 8`
print_nicely(a=7, y=8)fn print_nicely(**kwargs: Int) raises:
for key in kwargs.keys():
print(key[], "=", kwargs[key[]])
# prints:
# `a = 7`
# `y = 8`
print_nicely(a=7, y=8)For more details (and a list of current limitations), see Variadic keyword arguments in the Mojo manual.
🦋 Changed or removed
-
let
declarations now produce a compile time error instead of a warning, our next step in removing let declarations. The compiler still recognizes thelet
keyword for now in order to produce a good error message, but that will be removed in subsequent releases. -
Mojo now warns about unused values in both
def
andfn
declarations, instead of completely disabling the warning indef
s. It never warns about unusedobject
orPythonObject
values, tying the warning to these types instead of the kind of function they are unused in. This will help catch API usage bugs indef
s and make imported Python APIs more ergonomic infn
s. -
For the time being, dynamic type values will be disabled in the language. For example, the following will now fail with an error:
var t = Int # dynamic type values not allowed
struct SomeType: ...
takes_type(SomeType) # dynamic type values not allowedvar t = Int # dynamic type values not allowed
struct SomeType: ...
takes_type(SomeType) # dynamic type values not allowedWe want to take a step back and (re)design type valued variables, existentials, and other dynamic features. This does not affect type valued parameters, so the following works as before:
alias t = Int # still 🔥
struct SomeType: ...
takes_type[SomeType]() # already 🔥
>fn uses_trait[T: SomeTrait](value: T): ... # still 🔥alias t = Int # still 🔥
struct SomeType: ...
takes_type[SomeType]() # already 🔥
>fn uses_trait[T: SomeTrait](value: T): ... # still 🔥 -
The
*_
expression in parameter expressions is now required to occur at the end of a positional parameter list, instead of being allowed in the middle.# No longer supported
alias FirstUnbound = SomeStruct[*_, 42]
alias MidUnbound = SomeStruct[7, *_, 6]
# Still supported
alias LastUnbound = SomeStruct[42, *_]# No longer supported
alias FirstUnbound = SomeStruct[*_, 42]
alias MidUnbound = SomeStruct[7, *_, 6]
# Still supported
alias LastUnbound = SomeStruct[42, *_]We narrowed this because we want to encourage type designers to get the order of parameters right, and want to extend
*_
to support keyword parameters as well in the future.
Standard library changes
⭐️ New
-
DynamicVector
has been renamed toList
, and has moved from thecollections.vector
module to thecollections.list
module. In addition:-
You can now construct a
List
from a variadic number of values. For example:var numbers = List[Int](1, 2, 3)
var numbers = List[Int](1, 2, 3)
-
List
andInlinedFixedVector
types now support negative indexing. This means that you can writevec[-1]
which is equivalent tovec[len(vec)-1]
. -
List.push_back()
has been removed. Please use theappend()
function instead.
-
-
The
print()
function now takessep
andend
keyword arguments. This means that you can write:print("Hello", "Mojo", sep=", ", end="!!!\n") # prints Hello, Mojo!!!
print("Hello", "Mojo", sep=", ", end="!!!\n") # prints Hello, Mojo!!!
sep
defaults to the empty string andend
defaults to "\n".Also, the
print_no_newline()
function has been removed. Please useprint(end="")
instead. -
The
FloatLiteral
type is now an infinite-precision nonmaterializable type. This means you can do compile-time calculations usingFloatLiteral
without rounding errors. When materialized at runtime, aFloatLiteral
value is converted to aFloat64
.# third is an infinite-precision FloatLiteral value
alias third = 1.0 / 3.0
# t is a Float64
var t = third# third is an infinite-precision FloatLiteral value
alias third = 1.0 / 3.0
# t is a Float64
var t = third -
String types all conform to the
IntableRaising
trait. This means that you can now callint("123")
to get the integer123
. If the integer cannot be parsed from the string, then an error is raised. -
The
Tensor
type now hasargmax()
andargmin()
functions to compute the position of the max or min value. Note: this should return aTensor[Int]
but currently the output tensor is the same type as the input tensor. This will be fixed in a future release. -
Added a new
collections.OptionalReg
type, a register-passable alternative toOptional
. -
The
ulp()
function has been added to themath
module. This allows you to get the units of least precision (or units of last place) of a floating point value.
🦋 Changed
-
The
simd_load()
,simd_store()
,aligned_simd_load()
, andaligned_simd_store()
methods onDTypePointer
,Buffer
, andNDBuffer
have been merged into a more expressive set ofload()
andstore()
methods with keyword-onlywidth
andalignment
parameters:# Doesn't work
my_simd = my_buffer.simd_load[simd_width](index)
# Works
my_simd = my_buffer.load[width=simd_width](index)
# Doesn't work
my_buffer.aligned_simd_store[width, alignment](my_simd)
# Works
my_buffer.store[width=width, alignment=alignment](my_simd)# Doesn't work
my_simd = my_buffer.simd_load[simd_width](index)
# Works
my_simd = my_buffer.load[width=simd_width](index)
# Doesn't work
my_buffer.aligned_simd_store[width, alignment](my_simd)
# Works
my_buffer.store[width=width, alignment=alignment](my_simd) -
The
EqualityComparable
trait now requires the__ne__()
method for conformance in addition to the previously required__eq__()
method. -
Many types now declare conformance to
EqualityComparable
trait. -
StaticTuple
parameter order has changed toStaticTuple[type, size]
for consistency withSIMD
and similar collection types. -
The signature of the
elementwise()
function has been changed. The new order is isfunction
,simd_width
, and thenrank
. As a result, the rank parameter can now be inferred and one can callelementwise()
without it:elementwise[func, simd_width](shape)
elementwise[func, simd_width](shape)
-
PythonObject
is now register-passable. -
PythonObject.__iter__()
now works correctly on more types of iterable Python objects. Attempting to iterate over non-iterable objects will now raise an exception instead of behaving as if iterating over an empty sequence.__iter__()
also now borrowsself
rather than requiringinout
, allowing code like:for value in my_dict.values():
...for value in my_dict.values():
...
🚚 Moved
-
We took the opportunity to rehome some modules into their correct package as we were going through the process of open-sourcing the Mojo standard library. Specifically, the following are some breaking changes worth calling out. Please update your import statements accordingly.
-
Buffer
,NDBuffer
, and friends have moved from thememory
package into a newbuffer
package.from buffer import Buffer, NDBuffer
from buffer import Buffer, NDBuffer
-
utils.list
, including theDim
andDimList
types, has moved to thebuffer
package.from buffer import Dim, DimList
from buffer import Dim, DimList
-
The
parallel_memcpy()
function has moved from thememory
package into thebuffer
package.from buffer import parallel_memcpy
from buffer import parallel_memcpy
-
The
rand()
andrandn()
functions from therandom
package that return aTensor
have moved to thetensor
package. Note that the overloads that write to aDTypePointer
remain in therandom
package.If you happen to be using both versions in the same source file, you can import them both using the
import as
syntax:from tensor import rand
from random import rand as rand_dtfrom tensor import rand
from random import rand as rand_dt -
The
trap()
function has been renamed toabort()
. It also has moved from thedebug
module to theos
module.from os import abort
from os import abort
-
The
isinf()
andisfinite()
methods have been moved frommath.limits
to themath
module.from math import ininf, isfinite
from math import ininf, isfinite
-
Tooling changes
⭐️ New
-
Docstring code blocks can now use
%#
to hide lines of code from documentation generation.For example:
var value = 5
%# print(value)var value = 5
%# print(value)Will generate documentation of the form:
var value = 5
var value = 5
Hidden lines are processed as if they were normal code lines during test execution. This allows for writing additional code within a docstring example that is only used to ensure the example is runnable/testable.
-
The Mojo LSP server now allow you to specify additional search paths to use when resolving imported modules in a document. You can specify search paths on the command line, using the
-I
option, or you can add them to themojo.lsp.includeDirs
setting in the VS Code extension.
Other changes
❌ Removed
-
The
__get_address_as_lvalue
magic function has been removed. You can now get an LValue from aPointer
orReference
by using the dereference operator ([]
):var ptr: Pointer[MyRecord]
...
# Doesn't work
__get_address_as_lvalue(ptr.value) = MyRecord(3, 5)
# Works
ptr[] = MyRecord(3, 5)var ptr: Pointer[MyRecord]
...
# Doesn't work
__get_address_as_lvalue(ptr.value) = MyRecord(3, 5)
# Works
ptr[] = MyRecord(3, 5) -
The type parameter for the
memcpy
function is now automatically inferred. This means that calls tomemcpy
of the formmemcpy[Dtype.xyz](...)
will no longer work and the user would have to change the code tomemcpy(...)
. -
The
memcpy()
overload that worked onBuffer
types has been removed in favor of just overloads forPointer
andDTypePointer
:# Doesn't work
memcpy(destBuffer, srcBuffer, count)
# Works
memcpy(destBuffer.data, srcBuffer.data, count)# Doesn't work
memcpy(destBuffer, srcBuffer, count)
# Works
memcpy(destBuffer.data, srcBuffer.data, count) -
The functions
max_or_inf()
,min_or_neginf()
have been removed frommath.limit
. These functions were only used by the SIMD type. -
As mentioned previously, the
print_no_newline()
function has been removed. Please useprint(end="")
instead.
🛠️ Fixed
- #1362 - Parameter inference now recursively matches function types.
- #951 - Functions that were
both
async
and@always_inline
incorrectly errored. - #1858 - Trait with parametric methods regression.
- #1892 - Forbid unsupported decorators on traits.
- #1735 - Trait-typed values are incorrectly considered equal.
- #1909 - Crash due to nested import in unreachable block.
- #1921 - Parser crashes binding Reference to lvalue with subtype lifetime.
- #1945 -
Optional[T].or_else()
should returnT
instead ofOptional[T]
. - #1940 - Constrain
math.copysign
to floating point or integral types. - #1838 - Variadic
print
does not work when specifyingend=""
- #1826 - The
SIMD.reduce
methods correctly handle edge cases wheresize_out >= size
.
v24.1.1 (2024-03-18)
This release includes installer improvements and enhanced error reporting for installation issues. Otherwise it is functionally identical to Mojo 24.1.
v24.1 (2024-02-29)
🔥 Legendary
-
Mojo is now bundled with the MAX platform!
As such, the Mojo package version now matches the MAX version, which follows a
YY.MAJOR.MINOR
version scheme. Because this is our first release in 2024, that makes this version24.1
. -
Mojo debugging support is here! The Mojo VS Code extension includes debugger support. For details, see Debugging in the Mojo Manual.
⭐️ New
-
We now have a
Set
type in our collections!Set
is backed by aDict
, so it has fast add, remove, andin
checks, and requires member elements to conform to theKeyElement
trait.from collections import Set
var set = Set[Int](1, 2, 3)
print(len(set)) # 3
set.add(4)
for element in set:
print(element[])
set -= Set[Int](3, 4, 5)
print(set == Set[Int](1, 2)) # True
print(set | Set[Int](0, 1) == Set[Int](0, 1, 2)) # True
let element = set.pop()
print(len(set)) # 1from collections import Set
var set = Set[Int](1, 2, 3)
print(len(set)) # 3
set.add(4)
for element in set:
print(element[])
set -= Set[Int](3, 4, 5)
print(set == Set[Int](1, 2)) # True
print(set | Set[Int](0, 1) == Set[Int](0, 1, 2)) # True
let element = set.pop()
print(len(set)) # 1 -
Mojo now supports the
x in y
expression as syntax sugar fory.__contains__(x)
as well asx not in y
. -
Mojo now has support for keyword-only arguments and parameters. For example:
fn my_product(a: Int, b: Int = 1, *, c: Int, d: Int = 2):
print(a * b * c * d)
my_product(3, c=5) # prints '30'
my_product(3, 5, d=7) # error: missing 1 required keyword-only argument: 'c'fn my_product(a: Int, b: Int = 1, *, c: Int, d: Int = 2):
print(a * b * c * d)
my_product(3, c=5) # prints '30'
my_product(3, 5, d=7) # error: missing 1 required keyword-only argument: 'c'This includes support for declaring signatures that use both variadic and keyword-only arguments/parameters. For example, the following is now possible:
fn prod_with_offset(*args: Int, offset: Int = 0) -> Int:
var res = 1
for i in range(len(args)):
res *= args[i]
return res + offset
print(prod_with_offset(2, 3, 4, 10)) # prints 240
print(prod_with_offset(2, 3, 4, offset=10)) # prints 34fn prod_with_offset(*args: Int, offset: Int = 0) -> Int:
var res = 1
for i in range(len(args)):
res *= args[i]
return res + offset
print(prod_with_offset(2, 3, 4, 10)) # prints 240
print(prod_with_offset(2, 3, 4, offset=10)) # prints 34Note that variadic keyword-only arguments/parameters (for example,
**kwargs
) are not supported yet. That is, the following is not allowed:fn variadic_kw_only(a: Int, **kwargs): ...
fn variadic_kw_only(a: Int, **kwargs): ...
For more information, see Positional-only and keyword-only arguments in the Mojo Manual.
-
The
print()
function now accepts a keyword-only argument for theend
which is useful for controlling whether a newline is printed or not after printing the elements. By default,end
defaults to "\n" as before. -
The Mojo SDK can now be installed on AWS Graviton instances.
-
A new version of the Mojo Playground is available. The new playground is a simple interactive editor for Mojo code, similar to the Rust Playground or Go Playground. The old JupyterLab based playground will remain online until March 20th.
-
The Mojo LSP server will now generate fixits for populating empty documentation strings:
fn foo(arg: Int):
"""""" # Unexpected empty documentation stringfn foo(arg: Int):
"""""" # Unexpected empty documentation stringApplying the fixit from above will generate:
fn foo(arg: Int):
"""[summary].
Args:
arg: [description].
"""fn foo(arg: Int):
"""[summary].
Args:
arg: [description].
""" -
Added new
*_
syntax that allows users to explicitly unbind any number of positional parameters. For example:struct StructWithDefault[a: Int, b: Int, c: Int = 8, d: Int = 9]: pass
alias all_unbound = StructWithDefault[*_]
# equivalent to
alias all_unbound = StructWithDefault[_, _, _, _]
alias first_bound = StructWithDefault[5, *_]
# equivalent to
alias first_bound = StructWithDefault[5, _, _, _]
alias last_bound = StructWithDefault[*_, 6]
# equivalent to
alias last_bound = StructWithDefault[_, _, _, 6]
alias mid_unbound = StructWithDefault[3, *_, 4]
# equivalent to
alias mid_unbound = StructWithDefault[3, _, _, 4]struct StructWithDefault[a: Int, b: Int, c: Int = 8, d: Int = 9]: pass
alias all_unbound = StructWithDefault[*_]
# equivalent to
alias all_unbound = StructWithDefault[_, _, _, _]
alias first_bound = StructWithDefault[5, *_]
# equivalent to
alias first_bound = StructWithDefault[5, _, _, _]
alias last_bound = StructWithDefault[*_, 6]
# equivalent to
alias last_bound = StructWithDefault[_, _, _, 6]
alias mid_unbound = StructWithDefault[3, *_, 4]
# equivalent to
alias mid_unbound = StructWithDefault[3, _, _, 4]As demonstrated above, this syntax can be used to explicitly unbind an arbitrary number of parameters, at the beginning, at the end, or in the middle of the operand list. Since these unbound parameters must be explicitly specified at some point, default values for these parameters are not applied. For example:
alias last_bound = StructWithDefault[*_, 6]
# When using last_bound, you must specify a, b, and c. last_bound
# doesn't have a default value for `c`.
var s = last_bound[1, 2, 3]()alias last_bound = StructWithDefault[*_, 6]
# When using last_bound, you must specify a, b, and c. last_bound
# doesn't have a default value for `c`.
var s = last_bound[1, 2, 3]()For more information see the Mojo Manual sections on partially-bound types and automatic parameterization of functions.
-
DynamicVector
now supports iteration. Iteration values are instances of Reference and require dereferencing:var v: DynamicVector[String]()
v.append("Alice")
v.append("Bob")
v.append("Charlie")
for x in v:
x[] = str("Hello, ") + x[]
for x in v:
print(x[])var v: DynamicVector[String]()
v.append("Alice")
v.append("Bob")
v.append("Charlie")
for x in v:
x[] = str("Hello, ") + x[]
for x in v:
print(x[]) -
The
mojo package
command now produces compilation agnostic packages. Compilation options such as O0, or --debug-level, are no longer needed or accepted. As a result, packages are now smaller, and extremely portable. -
Initializers for
@register_passable
values can (and should!) now be specified withinout self
arguments just like memory-only types:@register_passable
struct YourPair:
var a: Int
var b: Int
fn __init__(inout self):
self.a = 42
self.b = 17
fn __copyinit__(inout self, existing: Self):
self.a = existing.a
self.b = existing.b@register_passable
struct YourPair:
var a: Int
var b: Int
fn __init__(inout self):
self.a = 42
self.b = 17
fn __copyinit__(inout self, existing: Self):
self.a = existing.a
self.b = existing.bThis form makes the language more consistent, more similar to Python, and easier to implement advanced features for. There is also no performance impact of using this new form: the compiler arranges to automatically return the value in a register without requiring you to worry about it.
The older
-> Self
syntax is still supported in this release, but will be removed in a subsequent one, so please migrate your code. One thing to watch out for: a given struct should use one style or the other, mixing some of each won't work well. -
The
inout self
initializer form is required for initializers of@register_passable
types that may raise errors:@register_passable
struct RaisingCtor:
fn __init__(inout self) raises:
raise@register_passable
struct RaisingCtor:
fn __init__(inout self) raises:
raise -
async
functions that may raise errors have been temporarily disabled in this build. The implementation of Mojo async is undergoing a rework 🚧. -
The standard library
slice
type has been renamed toSlice
, and aslice
function has been introduced. This makes Mojo closer to Python and makes theSlice
type follow the naming conventions of other types likeInt
. -
"Slice" syntax in subscripts is no longer hard coded to the builtin
slice
type: it now works with any type accepted by a container's__getitem__()
method. For example:@value
struct UnusualSlice:
var a: Int
var b: Float64
var c: String
struct YourContainer:
fn __getitem__(self, slice: UnusualSlice) -> T: ...@value
struct UnusualSlice:
var a: Int
var b: Float64
var c: String
struct YourContainer:
fn __getitem__(self, slice: UnusualSlice) -> T: ...Given this implementation, you can subscript into an instance of
YourContainer
likeyc[42:3.14:"🔥"]
and the three values are passed to theUnusualSlice
constructor. -
The
__refitem__()
accessor method may now return aReference
instead of having to return an MLIR internal reference type. -
Added
AnyPointer.move_into()
method, for moving a value from one pointer memory location to another. -
Added built-in
hex()
function, which can be used to format any value whose type implements theIntable
trait as a hexadecimal string. -
PythonObject
now implements__is__
and__isnot__
so that you can use expressions of the formx is y
andx is not y
withPythonObject
. -
PythonObject
now conforms to theSizedRaising
trait. This means the built-inlen()
function now works onPythonObject
. -
The
os
package now contains thestat()
andlstat()
functions. -
A new
os.path
package now allows you to query properties on paths. -
The
os
package now has aPathLike
trait. A struct conforms to thePathLike
trait by implementing the__fspath__()
function. -
The
pathlib.Path
now has functions to query properties of the path. -
The
listdir()
method now exists onpathlib.Path
and also exists in theos
module to work onPathLike
structs. For example, the following sample lists all the directories in the/tmp
directory:from pathlib import Path
fn walktree(top: Path, inout files: DynamicVector[Path]):
try:
var ls = top.listdir()
for i in range(len(ls)):
var child = top / ls[i]
if child.is_dir():
walktree(child, files)
elif child.is_file():
files.append(child)
else:
print("Skipping '" + str(child) + "'")
except:
return
fn main():
var files = DynamicVector[Path]()
walktree(Path("/tmp"), files)
for i in range(len(files)):
print(files[i])from pathlib import Path
fn walktree(top: Path, inout files: DynamicVector[Path]):
try:
var ls = top.listdir()
for i in range(len(ls)):
var child = top / ls[i]
if child.is_dir():
walktree(child, files)
elif child.is_file():
files.append(child)
else:
print("Skipping '" + str(child) + "'")
except:
return
fn main():
var files = DynamicVector[Path]()
walktree(Path("/tmp"), files)
for i in range(len(files)):
print(files[i]) -
The
find()
,rfind()
,count()
, and__contains__()
methods now work on string literals. This means that you can write:if "Mojo" in "Hello Mojo":
...if "Mojo" in "Hello Mojo":
... -
Breakpoints can now be inserted programmatically within the code using the builtin
breakpoint()
function.Note: on Graviton instances, the debugger might not be able to resume after hitting this kind of breakpoint.
-
Added a builtin
Boolable
trait that describes a type that can be represented as a boolean value. To conform to the trait, a type must implement the__bool__()
method. -
Modules within packages can now use purely relative
from
imports:from . import another_module
from . import another_module
-
Trivial types, like MLIR types and function types, can now be bound implicitly to traits that require copy constructors or move constructors, such as
Movable
,Copyable
, andCollectionElement
. -
A new magic
__origin_of(expr)
call will yield the lifetime of a memory value. We hope and expect that this will eventually be replaced byReference(expr).lifetime
as the parameter system evolves, but this is important in the meantime for use in function signatures. -
A new magic
__type_of(expr)
call will yield the type of a value. This allows one to refer to types of other variables. For example:fn my_function(x: Int, y: __type_of(x)) -> Int:
let z: __type_of(x) = y
return zfn my_function(x: Int, y: __type_of(x)) -> Int:
let z: __type_of(x) = y
return z
🦋 Changed
-
As another step towards removing let declarations we have removed support for let declarations inside the compiler. To ease migration, we parse
let
declarations as avar
declaration so your code won't break. We emit a warning about this, but please switch your code to usingvar
explicitly, because this migration support will be removed in a subsequent update.fn test():
# treated as a var, but please update your code!
let x = 42 # warning: 'let' is being removed, please use 'var' instead
x = 9fn test():
# treated as a var, but please update your code!
let x = 42 # warning: 'let' is being removed, please use 'var' instead
x = 9 -
It is no longer possible to explicitly specify implicit argument parameters in automatically parameterized functions. This ability was an oversight and this is now an error:
fn autoparameterized(x: SIMD):
pass
autoparameterized[DType.int32, 1](3) # error: too many parametersfn autoparameterized(x: SIMD):
pass
autoparameterized[DType.int32, 1](3) # error: too many parameters -
vectorize_unroll
has been removed, andvectorize
now has a parameter namedunroll_factor
with a default value of 1. Increasingunroll_factor
may improve performance at the cost of binary size. See the loop unrolling blog here for more details. -
The
vectorize
signatures have changed with the closurefunc
moved to the first parameter:vectorize[func, width, unroll_factor = 1](size)
vectorize[func, width, size, unroll_factor = 1]()vectorize[func, width, unroll_factor = 1](size)
vectorize[func, width, size, unroll_factor = 1]()The doc string has been updated with examples demonstrating the difference between the two signatures.
-
The
unroll
signatures have changed with the closurefunc
moved to the first parameter:unroll[func, unroll_count]()
unroll[func, unroll_count]()
-
The signature of the
NDBuffer
andBuffer
types have changed. Now, both take the type as the first parameter and no longer require the shape parameter. This allows you to use these types and have sensible defaults. For example:NDBuffer[DType.float32, 3]
NDBuffer[DType.float32, 3]
is equivalent to
NDBuffer[DType.float32, 3, DimList.create_unknown[3]()]
NDBuffer[DType.float32, 3, DimList.create_unknown[3]()]
Users can still specify the static shape (if known) to the type:
NDBuffer[DType.float32, 3, DimList(128, 128, 3)]
NDBuffer[DType.float32, 3, DimList(128, 128, 3)]
-
The error message for missing function arguments is improved: instead of describing the number of arguments (e.g.
callee expects at least 3 arguments, but 1 was specified
) the missing arguments are now described by name (e.g.missing 2 required positional arguments: 'b', 'c'
). -
The
CollectionElement
trait is now a built-in trait and has been removed fromcollections.vector
. -
The
DynamicVector(capacity: Int)
constructor has been changed to takecapacity
as a keyword-only argument to prevent implicit conversion fromInt
. -
Variant.get[T]()
now returns aReference
to the value rather than a copy. -
The
String
methodstolower()
andtoupper()
have been renamed tostr.lower()
andstr.upper()
. -
The
ref
andmutref
identifiers are no longer reserved as Mojo keywords. We originally thought about using those as language sugar for references, but we believe that generic language features combined with theReference
type will provide a good experience without dedicated sugar.
🛠️ Fixed
- #435 Structs with Self type don't always work.
- #1540 Crash in register_passable self referencing struct.
- #1664 - Improve error
message when
StaticTuple
is constructed with a negative size for the number of elements. - #1679 - crash on SIMD of zero elements.
- Various crashes on invalid code: #1230, #1699, #1708
- #1223 - Crash when parametric function is passed as (runtime) argument. The parser now errors out instead.
- #1530 - Crash during diagnostic emission for parameter deduction failure.
- #1538 and #1607 - Crash when returning type value instead of instance of expected type. This is a common mistake and the error now includes a hint to point users to the problem.
- #1613 - Wrong type name in
error for incorrect
self
argument type in trait method declaration. - #1670 - Crash on implicit conversion in a global variable declaration.
- #1741 - Mojo documentation
generation doesn't show
inout
/owned
on variadic arguments. - #1621 - VS Code does not
highlight
raises
andcapturing
in functional type expressions. - #1617 - VS Code does not
highlight
fn
in specific contexts. - #1740 - LSP shows unrelated info when hovering over a struct.
- #1238 - File shadows Mojo package path.
- #1429 - Crash when using nested import statement.
- #1322 - Crash when missing types in variadic argument.
- #1314 - Typecheck error when binding alias to parametric function with default argument.
- #1248 - Crash when importing from file the same name as another file in the search path.
- #1354 - Crash when importing from local package.
- #1488 - Crash when setting generic element field.
- #1476 - Crash in interpreter when calling functions in parameter context.
- #1537 - Crash when copying parameter value.
- #1546 - Modify nested vector element crashes parser.
- #1558 - Invalid import causes parser to crash.
- #1562 - Crash when calling parametric type member function.
- #1577 - Crash when using unresolved package as a variable.
- #1579 - Member access into type instances causes a crash.
- #1602 - Interpreter failure when constructing strings at compile time.
- #1696 - Fixed an issue that caused syntax highlighting to occasionally fail.
- #1549 - Fixed an issue when
the shift amount is out of range in
SIMD.shift_left
andSIMD.shift_right
.
v0.7.0 (2024-01-25)
⭐️ New
-
A new Mojo-native dictionary type,
Dict
for storing key-value pairs.Dict
stores values that conform to theCollectionElement
trait. Keys need to conform to the newKeyElement
trait, which is not yet implemented by other standard library types. In the short term, you can create your own wrapper types to use as keys. For example, the following sample defines aStringKey
type and uses it to create a dictionary that maps strings toInt
values:from collections.dict import Dict, KeyElement
@value
struct StringKey(KeyElement):
var s: String
fn __init__(inout self, owned s: String):
self.s = s ^
fn __init__(inout self, s: StringLiteral):
self.s = String(s)
fn __hash__(self) -> Int:
return hash(self.s)
fn __eq__(self, other: Self) -> Bool:
return self.s == other.s
fn main() raises:
var d = Dict[StringKey, Int]()
d["cats"] = 1
d["dogs"] = 2
print(len(d)) # prints 2
print(d["cats"]) # prints 1
print(d.pop("dogs")) # prints 2
print(len(d)) # prints 1from collections.dict import Dict, KeyElement
@value
struct StringKey(KeyElement):
var s: String
fn __init__(inout self, owned s: String):
self.s = s ^
fn __init__(inout self, s: StringLiteral):
self.s = String(s)
fn __hash__(self) -> Int:
return hash(self.s)
fn __eq__(self, other: Self) -> Bool:
return self.s == other.s
fn main() raises:
var d = Dict[StringKey, Int]()
d["cats"] = 1
d["dogs"] = 2
print(len(d)) # prints 2
print(d["cats"]) # prints 1
print(d.pop("dogs")) # prints 2
print(len(d)) # prints 1We plan to add
KeyElement
conformance to standard library types in subsequent releases. -
Users can opt-in to assertions used in the standard library code by specifying
-D MOJO_ENABLE_ASSERTIONS
when invokingmojo
to compile your source file(s). In the case that an assertion is fired, the assertion message will be printed along with the stack trace before the program exits. By default, assertions are not enabled in the standard library right now for performance reasons. -
The Mojo Language Server now implements the References request. IDEs use this to provide support for Go to References and Find All References. A current limitation is that references outside of the current document are not supported, which will be addressed in the future.
-
The
sys.info
module now includesnum_physical_cores()
,num_logical_cores()
, andnum_performance_cores()
functions. -
Homogeneous variadic arguments consisting of memory-only types, such as
String
are more powerful and easier to use. These arguments are projected into aVariadicListMem
.(Previous releases made it easier to use variadic lists of register-passable types, like
Int
.)Subscripting into a
VariadicListMem
now returns the element instead of an obscure internal type. In addition, we now supportinout
andowned
variadic arguments:fn make_worldly(inout *strs: String):
# This "just works" as you'd expect!
for i in range(len(strs)):
strs[i] += " world"
fn main():
var s1: String = "hello"
var s2: String = "konnichiwa"
var s3: String = "bonjour"
make_worldly(s1, s2, s3)
print(s1) # hello world
print(s2) # konnichiwa world
print(s3) # bonjour worldfn make_worldly(inout *strs: String):
# This "just works" as you'd expect!
for i in range(len(strs)):
strs[i] += " world"
fn main():
var s1: String = "hello"
var s2: String = "konnichiwa"
var s3: String = "bonjour"
make_worldly(s1, s2, s3)
print(s1) # hello world
print(s2) # konnichiwa world
print(s3) # bonjour world(Previous releases made it easier to use variadic lists, but subscripting into a
VariadicListMem
returned a low-level pointer, which required the user to call__get_address_as_lvalue()
to access the element.)Note that subscripting the variadic list works nicely as above, but iterating over the variadic list directly with a
for
loop produces aReference
(described below) instead of the desired value, so an extra subscript is required; We intend to fix this in the future.fn make_worldly(inout *strs: String):
# Requires extra [] to dereference the reference for now.
for i in strs:
i[] += " world"fn make_worldly(inout *strs: String):
# Requires extra [] to dereference the reference for now.
for i in strs:
i[] += " world"Heterogeneous variadic arguments have not yet been moved to the new model, but will in future updates.
Note that for variadic arguments of register-passable types like
Int
, the variadic list contains values, not references, so the dereference operator ([]
) is not required. This code continues to work as it did previously:fn print_ints(*nums: Int):
for num in nums:
print(num)
print(len(nums))fn print_ints(*nums: Int):
for num in nums:
print(num)
print(len(nums)) -
Mojo now has a prototype version of a safe
Reference
type. The compiler's lifetime tracking pass can reason about references to safely extend local variable lifetime, and check indirect access safety. TheReference
type is brand new (and currently has no syntactic sugar) so it must be explicitly dereferenced with an empty subscript:ref[]
provides access to the underlying value.fn main():
var a: String = "hello"
var b: String = " references"
var aref = Reference(a)
aref[] += b
print(a) # prints "hello references"
aref[] += b
# ^last use of b, it is destroyed here.
print(aref[]) # prints "hello references references"
# ^last use of a, it is destroyed here.fn main():
var a: String = "hello"
var b: String = " references"
var aref = Reference(a)
aref[] += b
print(a) # prints "hello references"
aref[] += b
# ^last use of b, it is destroyed here.
print(aref[]) # prints "hello references references"
# ^last use of a, it is destroyed here.While the
Reference
type has the same in-memory representation as a C pointer or the MojoPointer
type, it also tracks a symbolic "lifetime" value so the compiler can reason about the potentially accessed set of values. This lifetime is part of the static type of the reference, so it propagates through generic algorithms and abstractions built around it.The
Reference
type can form references to both mutable and immutable memory objects, e.g. those on the stack or borrowed/inout/owned function arguments. It is fully parametric over mutability, eliminating the problems with code duplication due to mutability specifiers and provides the base for unified user-level types. For example, it could be used to implement an array slice object that handles both mutable and immutable array slices.While this is a major step forward for the lifetimes system in Mojo, it is still very early and awkward to use. Notably, there is no syntactic sugar for using references, such as automatic dereferencing. Several aspects of it need to be more baked. It is getting exercised by variadic memory arguments, which is why they are starting to behave better now.
Note: the safe
Reference
type and the unsafe pointer types are defined in the same module, currently namedmemory.unsafe
. We expect to restructure this module in a future release. -
Mojo now allows types to implement
__refattr__()
and__refitem__()
to enable attribute and subscript syntax with computed accessors that return references. For common situations where these address a value in memory this provides a more convenient and significantly more performant alternative to implementing the traditional get/set pairs. Note: this may be changed in the future when references auto-dereference—at that point we may switch to just returning a reference from__getattr__()
. -
Parametric closures can now capture register passable typed values by copy using the
__copy_capture
decorator. For example, the following code will print5
, not2
.fn foo(x: Int):
var z = x
@__copy_capture(z)
@parameter
fn formatter() -> Int:
return z
z = 2
print(formatter())
fn main():
foo(5)fn foo(x: Int):
var z = x
@__copy_capture(z)
@parameter
fn formatter() -> Int:
return z
z = 2
print(formatter())
fn main():
foo(5) -
String now implements KeyElement and may be used as a key in Dict.
-
More robust support for structs with fields of self referencing types. For example, the following code will work and print
0
:struct Foo(CollectionElement):
var vec: DynamicVector[Self]
fn __init__(inout self: Self):
self.vec = DynamicVector[Self]()
fn __moveinit__(inout self: Self, owned existing: Self):
self.vec = existing.vec ^
fn __copyinit__(inout self: Self, existing: Self):
self.vec = existing.vec
fn main():
var foo = Foo()
print(len(foo.vec))struct Foo(CollectionElement):
var vec: DynamicVector[Self]
fn __init__(inout self: Self):
self.vec = DynamicVector[Self]()
fn __moveinit__(inout self: Self, owned existing: Self):
self.vec = existing.vec ^
fn __copyinit__(inout self: Self, existing: Self):
self.vec = existing.vec
fn main():
var foo = Foo()
print(len(foo.vec))
❌ Removed
-
The
__takeinit__
special constructor form has been removed from the language. This "non-destructive move" operation was previously wired into thex^
transfer operator, but had unpredictable behavior that wasn't consistent. Now that Mojo has traits, it is better to model this as an explicit.take()
operation on a type, which would transfer out the contents of the type without ending its lifetime. For example, for a type that holds a pointer,take()
might return a new instance pointing to the same data, and null out its own internal pointer.This change makes it clear when a lifetime is ended versus when the contents of an LValue are explicitly taken.
-
The current implementation of autotuning has been deprecated, as Mojo's autotuning implementation is undergoing a redesign. Tutorials around the current implementation have also been removed as they are being rewritten.
Consequently, the
autotune()
,autotune_fork()
, andsearch()
functions have been removed from the standard library. -
The
_OldDynamicVector
type that worked only on register passable element types has been removed. Please migrate uses toDynamicVector
which works on both register passable and memory types. -
The
UnsafeFixedVector
inutils.vector
has been removed. We recommend using eitherDynamicVector
orInlinedFixedVector
instead. -
The
@adaptive
decorator has been removed from the language. Any uses of the decorator in a non-search context can be replaced with@parameter if
. For example:@adaptive
fn foo[a: Bool]():
constrained[a]()
body1()
@adaptive
fn foo[a: Bool]():
constrained[not a]()
body2()@adaptive
fn foo[a: Bool]():
constrained[a]()
body1()
@adaptive
fn foo[a: Bool]():
constrained[not a]()
body2()Can be rewritten as:
fn foo[a: Bool]():
@parameter
if a:
body1()
else:
body2()fn foo[a: Bool]():
@parameter
if a:
body1()
else:
body2()Consequently, the special
__adaptive_set
attribute has been removed as well. -
Result parameters have been removed from Mojo. Result parameter declarations in function parameter lists are no longer allowed, nor are forward alias declarations. This includes removing the
param_return
statement. -
The
@noncapturing
and@closure
decorators have been removed due to refinements and improvements to the closure model. See below for more details!
🦋 Changed
-
The Mojo closure model has been refined to be more straightforward and safe. Mojo has two closure types: parameter closures and runtime closures. Parameter closures can be used in higher-order functions and are the backbone of functions like
vectorize
andparallelize
. They are always denoted by@parameter
and have typefn() capturing -> T
(whereT
is the return type).On the other hand, runtime closures are always dynamic values, capture values by invoking their copy constructor, and retain ownership of their capture state. You can define a runtime closure by writing a nested function that captures values:
fn outer(b: Bool, x: String) -> fn() escaping -> None:
fn closure():
print(x) # 'x' is captured by calling String.__copyinit__
fn bare_function():
print("hello") # nothing is captured
if b:
# closure can be safely returned because it owns its state
return closure^
# function pointers can be converted to runtime closures
return bare_functionfn outer(b: Bool, x: String) -> fn() escaping -> None:
fn closure():
print(x) # 'x' is captured by calling String.__copyinit__
fn bare_function():
print("hello") # nothing is captured
if b:
# closure can be safely returned because it owns its state
return closure^
# function pointers can be converted to runtime closures
return bare_functionThe type of runtime closures are of the form
fn() escaping -> T
. You can pass equivalent function pointers as runtime closures.Stay tuned for capture list syntax for move capture and capture by reference, and a more unified closure model!
-
The
@unroll(n)
decorator can now take a parameter expression for the unroll factor, i.e.n
can be a parameter expression that is of integer type. -
The
cpython
module in thepython
package has been moved to be an internal module, i.e,_cpython
. -
AnyType
andDestructable
have been unified into a single trait,AnyType
. Every nominal type (i.e. all structs) now automatically conform toAnyType
. -
Previously, the
mojo package
command would output a Mojo package that included both partly-compiled Mojo code, as well as fully-compiled machine code for a specific computer architecture -- the architecture of the machine being used to invoke themojo package
command.Now,
mojo package
only includes partly-compiled Mojo code. It is only fully compiled for the specific computer architecture being used at the point that the package is firstimport
-ed. As a result, Mojo packages are smaller and more portable. -
The
simd_width
anddtype
parameters ofpolynomial_evaluate
have been switched. Based on the request in #1587, thepolynomial_evaluate
function has also been extended so that thecoefficients
parameter can take either a either aStaticTuple
or aVariadicList
. -
As a tiny step towards removing
let
declarations, this release removes the warning:'var' was never mutated, consider switching to a 'let'
.
🛠️ Fixed
- #1595 - Improve error message
when trying to materialize
IntLiteral
in runtime code. - Raising an error from the initializer of a memory-only type now works
correctly in the presence of complex control flow. Previously Mojo could run
the destructor on
self
before it was initialized when exiting with an error. - #1096 - Improve warning
messages for dead code in conditionals like
or
expressions. - #1419 - Fix assertion failure with uninitialized lattice values.
- #1402 - Fix movable trait not
detected on recursive struct implemented with
AnyPointer
. - #1399 - Fix parser crash when a parameter type in a struct that implements a trait is misspelled.
- #1152 - Allow mutable
self
argument when overloading operators using dunder methods. - #1493 - Fix crash in
DynamicVector
copy constructor in certain situations. - #1316 - The
benchmark.keep
function now properly handles vector types. - #1505 - The
simd.shuffle
operation now works on 64 element permutations. - #1355 - Fix
String.find()
returning wrong value when starting index is non-zero. - #1367 - Fix
String.replace()
returning incorrect results for multi-character search strings. - #1535 - Invalid error
field 'w.x.y' destroyed out of the middle of a value, preventing the overall value from being destroyed
. - #1475 - Assertion failure in nested loop.
- #1591 - Assertion failure
when using
AnyType
struct member. - #1503 - Rename the mojo build
of LLDB to
mojo-lldb
, to prevent name collisions with the system's LLDB. - #1542 -
@unroll
does not accept alias as unroll factor. - #1443 - Compiler crash on variadic list of traits.
- #1604 - Variable of trivial type not destroyed by transferring ownership.
- #1341 - Segmentation fault when passing closures around.
- #217 - Closure state is stack allocated.
v0.6.1 (2023-12-18)
⭐️ New
-
The Mojo REPL now provides limited support for the
%cd
magic command.This command automatically maintains an internal stack of directories you visit during the REPL session. Usage:
%cd 'dir'
: change to directorydir
and push it on the directory stack.%cd -
: pop the directory stack and change to the last visited directory.
-
Structs decorated with
@value
now automatically conform to theMovable
andCopyable
built-in traits. -
String
now has newtoupper()
andtolower()
methods analogous, respectively, to Python'sstr.toupper()
andstr.tolower()
. -
Added a
hash()
built-in function andHashable
trait for types implementing the__hash__()
method. Future releases will addHashable
support to Standard Library types. In the meantime, thehash
module includes a version of thehash()
function that works on arbitrary byte strings. To generate hashes forSIMD
types, you use the internal_hash_simd()
function:from builtin.hash import _hash_simd
fn gen_simd_hash():
let vector = SIMD[DType.int64, 4](1, 2, 3, 4)
let hash = _hash_simd(vector)from builtin.hash import _hash_simd
fn gen_simd_hash():
let vector = SIMD[DType.int64, 4](1, 2, 3, 4)
let hash = _hash_simd(vector) -
Several standard library types now conform to the
CollectionElement
trait. These types includeBool
,StringLiteral
,DynamicVector
,Tensor
,TensorShape
, andTensorSpec
.
🦋 Changed
-
utils.vector
has been moved to a newcollections
package to make space for new collections. This means that if you had previous code that didfrom utils.vector import DynamicVector
, it now needs to befrom collections.vector import DynamicVector
due to the move. -
The special destructor method
__del__()
has been changed to enforce that it cannot raise an error. Raising destructors are not supported properly at the moment.
🛠️ Fixed
-
#1421 - Fixed a crash when using Tuples in the REPL.
-
#222 - Generate an error for obviously self recursive functions.
-
#1408 - Fix overload resolution when candidates can return generic types.
-
#1413 and #1395 - Do not crash when re-declaring a builtin declaration.
-
#1307 - Fix compatibility of function signatures that only differ in default argument values.
-
#1380 - Fix printing of empty
String
.
v0.6.0 (2023-12-04)
🔥 Legendary
-
Traits have arrived!
You can now define a trait, which consists of a required set of method prototypes. A struct can conform to the trait by implementing these methods. This lets you write generic functions that work on any structs that conform to a given trait.
The following section gives a brief overview of traits—see the Mojo Manual and this traits blog post for more details!
Traits are declared with the
trait
keyword. The bodies of traits should contain method signatures declared with...
as their bodies. Default method implementations are not supported yet.trait SomeTrait:
fn required_method(self, x: Int): ...trait SomeTrait:
fn required_method(self, x: Int): ...The trait can be implemented on a struct by inheriting from it.
struct SomeStruct(SomeTrait):
fn required_method(self, x: Int):
print("hello traits", x)struct SomeStruct(SomeTrait):
fn required_method(self, x: Int):
print("hello traits", x)You can then write a generic functions that accepts any type that conforms to the trait. You do this by creating a parameterized function with a trait-typed parameter:
fn fun_with_traits[T: SomeTrait](x: T):
x.required_method(42)fn fun_with_traits[T: SomeTrait](x: T):
x.required_method(42)Which can be invoked with instances of types that conform to the trait:
var thing = SomeStruct()
# Infer the parameter `T`!
fun_with_traits(thing)var thing = SomeStruct()
# Infer the parameter `T`!
fun_with_traits(thing)Traits can also inherit from other traits, which simply requires that implementers of the child trait also conform to all parent traits.
trait Parent:
fn parent_func(self): ...
trait Child(Parent):
fn child_func(self): ...trait Parent:
fn parent_func(self): ...
trait Child(Parent):
fn child_func(self): ...Then, both child and parent trait methods can be invoked on instances of the trait
Child
. As well, an instance of the child trait can be converted to an instance of the parent trait.fn the_parents[T: Parent](x: T):
x.parent_func()
fn the_children[T: Child](x: T):
x.child_func()
x.parent_func()
# Upcast `x` from instance of `Child` to `Parent`.
the_parents(x)fn the_parents[T: Parent](x: T):
x.parent_func()
fn the_children[T: Child](x: T):
x.child_func()
x.parent_func()
# Upcast `x` from instance of `Child` to `Parent`.
the_parents(x)For more information, see the Traits page in the Mojo Manual.
-
A fundamental
Destructable
trait has been added to the language. This is a core trait that every trait automatically conforms to. This enables destruction of generic types and generic collections.Note: We're aware that this trait might be better spelled
Destructible
. We're planning on removing it in the future and moving its functionality toAnyType
so that any type that doesn't provide its own destructor will have a default, no-op destructor. -
We've added some traits to the standard library, you can implement these on your own types:
-
We added built-in
len()
,str()
, andint()
functions, which work with types that implement theSized
,Stringable
, andIntable
traits, respectively. -
DynamicVector
is now a proper generic collection that can use any type that implements theMovable
andCopyable
traits. This means you can now write, for example,DynamicVector[String]
. Also,DynamicVector
now invokes its element destructors upon destruction, so_del_old
has been deleted. -
print
now works on any types that implementStringable
by invoking their__str__
method:@value
struct BoxedInt(Stringable):
var value: Int
fn __str__(self) -> String:
return self.value
print(BoxedInt(11), "hello traits!", BoxedInt(42))@value
struct BoxedInt(Stringable):
var value: Int
fn __str__(self) -> String:
return self.value
print(BoxedInt(11), "hello traits!", BoxedInt(42))
⭐️ New
-
The Mojo Manual is an all-new, complete Mojo user guide. It doesn't include everything about Mojo yet, but it includes a lot, and more than the original programming manual (now deprecated).
Plus, the entire Mojo Manual and other Mojo docs are now open-sourced on GitHub, and we'd love to accept contributions to help us improve them!
-
Mojo now supports partial automatic parameterization: when a function is declared with an argument of a partially bound type, the unbound parameters of that type are implicitly added to the function's input parameters. For example:
@value
struct Fudge[a: Int, b: Int, c: Int = 7]: ...
# These function declarations are roughly equivalent:
fn eat(f: Fudge[5]): ... # implicitly parameterized
fn eat[_b: Int](f: Fudge[5, _b]): ... # explicitly parameterized@value
struct Fudge[a: Int, b: Int, c: Int = 7]: ...
# These function declarations are roughly equivalent:
fn eat(f: Fudge[5]): ... # implicitly parameterized
fn eat[_b: Int](f: Fudge[5, _b]): ... # explicitly parameterizedIn the first signature for
eat()
, theb
parameter isn't bound, so it's implicitly added as an input parameter on the function.In the second signature for
eat()
, the author has explicitly defined an input parameter (_b
), which is bound to the second parameter on the argument type (which happens to beb
).Both functions can be called like this:
eat(Fudge[5, 8]())
eat(Fudge[5, 8]())
Mojo infers the value of the
b
parameter from the argument (in this case, 8).With the second signature, you can also pass the
_b
parameter value explicitly:eat[3](Fudge[5, 3]())
eat[3](Fudge[5, 3]())
Moreover, Mojo now allows you to explicitly mark parameters as unbound using the
_
as syntax meaning "placeholder for an unbound parameter." For example:# These function declarations are roughly equivalent:
fn eat(f: Fudge[5, _, c=_]): ... # implicitly parameterized
fn eat(f: Fudge[c=_, a=5, b=_]): ... # implicitly parameterized
fn eat[_b: Int, _c: Int](f: Fudge[5, _b, _c]): ... # explicitly parameterized# These function declarations are roughly equivalent:
fn eat(f: Fudge[5, _, c=_]): ... # implicitly parameterized
fn eat(f: Fudge[c=_, a=5, b=_]): ... # implicitly parameterized
fn eat[_b: Int, _c: Int](f: Fudge[5, _b, _c]): ... # explicitly parameterizedThe first two signatures explicitly unbind the
b
andc
parameters.In the last signature, the
_b
and_c
parameters are explicitly declared by the author, and bound to theb
andc
parameters in the argument type.Any of these signatures can be called like this:
eat(Fudge[5, 8]())
eat(Fudge[5, 8, 9]())eat(Fudge[5, 8]())
eat(Fudge[5, 8, 9]())Note that the default parameter values of struct parameters are bound, unless explicitly unbound by the user.
For more information, see the Mojo Manual.
-
Parametric types can now be partially bound in certain contexts. For example, a new
Scalar
type alias has been added defined as:alias Scalar = SIMD[size=1]
alias Scalar = SIMD[size=1]
Which creates a parametric type alias
Scalar
with a single parameter of typeDType
. Types can also be partially or fully bound in other contexts. For instance,alias
declarations of type values inside functions now work properly:fn type_aliases():
alias T = SIMD
print(T[DType.float32, 1]())
alias Partial = T[type=DType.int32]
print(Partial[2]())fn type_aliases():
alias T = SIMD
print(T[DType.float32, 1]())
alias Partial = T[type=DType.int32]
print(Partial[2]()) -
The
__mlir_op
feature now supports operations that return multiple results. To use them, you write the_type
field as aTuple
of types. For example:# The `ret` variable has type `Tuple[Int, Int]`.
let ret = __mlir_op.`multi_result_op`[_type=(Int, Int)]()# The `ret` variable has type `Tuple[Int, Int]`.
let ret = __mlir_op.`multi_result_op`[_type=(Int, Int)]() -
Mojo now has the ability to read raw bytes from a file using the
read_bytes()
method. For example:with open("file.binary", "r") as f:
data = f.read_bytes()with open("file.binary", "r") as f:
data = f.read_bytes() -
A size argument was added to the
read()
andread_bytes()
methods on the builtinfile.FileHandle
. The size argument defaults to -1 and maintains the previous "read to EOF" behavior when size is negative.with open("file.binary", "r") as f:
data1 = f.read_bytes(1024)
data2 = f.read_bytes(256)with open("file.binary", "r") as f:
data1 = f.read_bytes(1024)
data2 = f.read_bytes(256) -
Path
now hasread_bytes()
andread_text()
methods to read file contents from a path:let text_path = Path("file.txt")
let text = text_path.read_text()
let binary_path = Path("file.binary")
let data = binary_path.read_bytes()let text_path = Path("file.txt")
let text = text_path.read_text()
let binary_path = Path("file.binary")
let data = binary_path.read_bytes() -
Tensor
has newsave()
andload()
methods to save and load to file. These methods preserve shape and datatype information. For example:let tensor = Tensor[DType.float32]()
tensor.save(path)
let tensor_from_file = Tensor[DType.float32].load(path)let tensor = Tensor[DType.float32]()
tensor.save(path)
let tensor_from_file = Tensor[DType.float32].load(path) -
Subscripting added to
DTypePointer
andPointer
:let p = DTypePointer[DType.float16].alloc(4)
for i in range(4):
p[i] = i
print(p[i])let p = DTypePointer[DType.float16].alloc(4)
for i in range(4):
p[i] = i
print(p[i]) -
file.FileHandle
now has aseek()
method. -
String
now has anrfind()
method analogous to Python'sstr.rfind()
. -
String
now has ansplit()
method analogous to Python'sstr.split()
. -
Path
now has asuffix()
method analogous to Python'spathlib.Path.suffix
. -
The Mojo REPL now supports indented expressions, making it a bit easier to execute expressions copied from an indented block (such as a doc string).
-
The Mojo Language Server now implements the Document Symbols request. IDEs use this to provide support for Outline View and Go to Symbol. This addresses Issue #960.
-
The Mojo Language Server now shows documentation when code completing modules or packages in
import
statements. -
The Mojo Language Server now supports processing code examples, defined as markdown Mojo code blocks, inside of doc strings. This enables IDE features while writing examples in API documentation.
-
The Mojo Language Server now provides semantic token information, providing better highlighting for symbols whose semantics are not statically analyzable.
-
The Mojo Language Server now classifies doc strings as folding ranges, making them easier to collapse, reducing vertical space while editing.
-
Command line options for the
mojo
driver that take arguments can now be written in either of two ways: both--foo FOO
and--foo=FOO
. Previously, only the former was valid.
🦋 Changed
-
Variadic list types
VariadicList
andVariadicListMem
are now iterable. Variadic arguments are automatically projected into one of these types inside the function body, so var args can be iterated:fn print_ints(*nums: Int):
for num in nums:
print(num)
print(len(nums))fn print_ints(*nums: Int):
for num in nums:
print(num)
print(len(nums)) -
The assert functions in the
testing
package now raise anError
when the assertion fails instead of returning aBool
for whether the assertion succeeded or not. -
Parameters of
AnyType
type are no longer (implicitly) assumed to be register-passable. A newAnyRegType
type is used to represent generic types that are register passable. -
Changing the units in a
benchmark
report is now an argument instead of a parameter:let report = benchmark.run[timer]()
report.print(Unit.ms)let report = benchmark.run[timer]()
report.print(Unit.ms) -
Default values on
inout
arguments are no longer permitted, i.e. the following will now raise an error:fn inout_default(inout x: Int = 2): ...
fn inout_default(inout x: Int = 2): ...
-
The
to_string()
function has been removed fromPythonObject
in favor of the new__str__()
function. This composes better with traits so it can be used with the genericstr()
function.
🛠️ Fixed
-
#734 - Consumption of struct works only for types with a
__del__
method. -
#910 - Parser crash when using memory-only generic type as return of function that
raise
s. -
#1060 - Mojo happily parses code that has messed up indentation
-
#1159 - The language server doesn't warn about bad return type.
-
#1166 - warning: unreachable code after return statement with context manager
-
#1098 - The language server doesn't highlight properties of PythonObjects correctly.
-
#1153 - The language server crashes when parsing an invalid multi-nested module import.
-
#1236 - The language server doesn't show autocomplete in if statements.
-
#1246 - Warning diagnostics are transient in the presence of caching.
Known Issue
- There is an issue affecting Jupyter notebooks that use autotuning and traits. This issue only manifests on macOS, and the same code runs without issue outside of the notebooks. This issue affects the Matrix multiplication in Mojo notebook.
v0.5.0 (2023-11-2)
⭐️ New
-
The
SIMD
type now defaults to the architectural SIMD width of the type. This means you can writeSIMD[DType.float32]
which is equivalent toSIMD[DType.float32, simdwidthof[DType.float32]()]
. -
The
SIMD
type now contains ajoin()
function that allows you to concatenate twoSIMD
values together and produce a newSIMD
value. -
Mojo now supports compile-time keyword parameters, in addition to existing support for keyword arguments. For example:
fn foo[a: Int, b: Int = 42]():
print(a, "+", b)
foo[a=5]() # prints '5 + 42'
foo[a=7, b=13]() # prints '7 + 13'
foo[b=20, a=6]() # prints '6 + 20'fn foo[a: Int, b: Int = 42]():
print(a, "+", b)
foo[a=5]() # prints '5 + 42'
foo[a=7, b=13]() # prints '7 + 13'
foo[b=20, a=6]() # prints '6 + 20'Keyword parameters are also supported in structs:
struct KwParamStruct[a: Int, msg: String = "🔥mojo🔥"]:
fn __init__(inout self):
print(msg, a)
fn use_kw_params():
KwParamStruct[a=42]() # prints '🔥mojo🔥 42'
KwParamStruct[5, msg="hello"]() # prints 'hello 5'
KwParamStruct[msg="hello", a=42]() # prints 'hello 42'struct KwParamStruct[a: Int, msg: String = "🔥mojo🔥"]:
fn __init__(inout self):
print(msg, a)
fn use_kw_params():
KwParamStruct[a=42]() # prints '🔥mojo🔥 42'
KwParamStruct[5, msg="hello"]() # prints 'hello 5'
KwParamStruct[msg="hello", a=42]() # prints 'hello 42'For more detail, see the Mojo Manual.
For the time being, the following notable limitations apply:
-
Keyword-only parameters are not supported yet:
fn baz[*args: Int, b: Int](): pass # fails
fn baz[a: Int, *, b: Int](): pass # failsfn baz[*args: Int, b: Int](): pass # fails
fn baz[a: Int, *, b: Int](): pass # fails(The analogous keyword-only arguments in Python are described in PEP 3102.)
-
Variadic keyword parameters are not supported yet:
fn baz[a: Int, **kwargs: Int](): pass # fails
fn baz[a: Int, **kwargs: Int](): pass # fails
-
-
Mojo now supports "automatic" parameterization of functions. What this means is that if a function argument type is parametric but has no bound parameters, they are automatically added as input parameters on the function. This works with existing features to allow you to write parametric functions with less boilerplate.
@value
struct Thing[x: Int, y: Int]:
pass
fn foo(v: Thing):
print(v.x)
print(v.y)
fn main():
let v = Thing[2, 3]()
foo(v)@value
struct Thing[x: Int, y: Int]:
pass
fn foo(v: Thing):
print(v.x)
print(v.y)
fn main():
let v = Thing[2, 3]()
foo(v)However, partial autoparameterization is not supported yet:
fn foo(v: Thing[y=7]): # Partially bound type not allowed yet.
...fn foo(v: Thing[y=7]): # Partially bound type not allowed yet.
... -
Keyword argument passing is supported when invoking
__getitem__
using the bracket syntax:@value
struct MyStruct:
fn __getitem__(self, x: Int, y: Int, z: Int) -> Int:
return x * y + z
MyStruct()[z=7, x=3, y=5] # returns 22@value
struct MyStruct:
fn __getitem__(self, x: Int, y: Int, z: Int) -> Int:
return x * y + z
MyStruct()[z=7, x=3, y=5] # returns 22However, keyword argument passing to
__setitem__
using the bracket syntax is not supported yet:@value
struct OtherStruct:
fn __setitem__(self, x: Int, y: Int): pass
OtherStruct()[x=1] = 4 # fails@value
struct OtherStruct:
fn __setitem__(self, x: Int, y: Int): pass
OtherStruct()[x=1] = 4 # fails -
Function argument input parameters can now be referenced within the signature of the function:
fn foo(x: SIMD, y: SIMD[x.type, x.size]):
passfn foo(x: SIMD, y: SIMD[x.type, x.size]):
pass -
The
benchmark
module has been simplified and improved so you can now run:import benchmark
from time import sleep
fn sleeper():
sleep(.01)
fn main():
let report = benchmark.run[sleeper]()
print(report.mean())import benchmark
from time import sleep
fn sleeper():
sleep(.01)
fn main():
let report = benchmark.run[sleeper]()
print(report.mean())It no longer requires a capturing
fn
so can benchmark functions outside the same scope.You can print a report with:
report.print()
report.print()
---------------------
Benchmark Report (s)
---------------------
Mean: 0.012314264957264957
Total: 1.440769
Iters: 117
Warmup Mean: 0.0119335
Warmup Total: 0.023866999999999999
Warmup Iters: 2
Fastest Mean: 0.012227958333333334
Slowest Mean: 0.012442699999999999---------------------
Benchmark Report (s)
---------------------
Mean: 0.012314264957264957
Total: 1.440769
Iters: 117
Warmup Mean: 0.0119335
Warmup Total: 0.023866999999999999
Warmup Iters: 2
Fastest Mean: 0.012227958333333334
Slowest Mean: 0.012442699999999999Units for all functions default to seconds, but can be changed with:
from benchmark import Unit
report.print[Unit.ms]()from benchmark import Unit
report.print[Unit.ms]() -
Mojo now supports struct parameter deduction (a.k.a. class template argument deduction, or CTAD) for partially bound types. Struct parameter deduction is also possible from static methods. For example:
@value
struct Thing[v: Int]: pass
struct CtadStructWithDefault[a: Int, b: Int, c: Int = 8]:
fn __init__(inout self, x: Thing[a]):
print("hello", a, b, c)
@staticmethod
fn foo(x: Thing[a]):
print("🔥", a, b, c)
fn main():
_ = CtadStructWithDefault[b=7](Thing[6]()) # prints 'hello 6 7 8'
CtadStructWithDefault[b=7].foo(Thing[6]()) # prints '🔥 6 7 8'@value
struct Thing[v: Int]: pass
struct CtadStructWithDefault[a: Int, b: Int, c: Int = 8]:
fn __init__(inout self, x: Thing[a]):
print("hello", a, b, c)
@staticmethod
fn foo(x: Thing[a]):
print("🔥", a, b, c)
fn main():
_ = CtadStructWithDefault[b=7](Thing[6]()) # prints 'hello 6 7 8'
CtadStructWithDefault[b=7].foo(Thing[6]()) # prints '🔥 6 7 8' -
Tensor
has newfromfile()
andtofile()
methods to save and load as bytes from a file. -
The built-in
print()
function now works on theTensor
type. -
TensorShape
andTensorSpec
now have constructors that takeDynamicVector[Int]
andIndexList
to initialize shapes. -
The
String
type now has thecount()
andfind()
methods to enable counting the number of occurrences or finding the offset index of a substring in a string. -
The
String
type now has areplace()
method which allows you to replace a substring with another string.
🦋 Changed
-
VariadicList
andVariadicListMem
moved under builtins, and no longer need to be imported. -
Variadic arguments are now automatically projected into a
VariadicList
orVariadicListMem
inside the function body. This allows for more flexibility in using var args. For example:fn print_ints(*nums: Int):
let len = len(nums)
for i in range(len):
print(nums[i])
print(len)fn print_ints(*nums: Int):
let len = len(nums)
for i in range(len):
print(nums[i])
print(len) -
The parameters for
InlinedFixedVector
have been switched. The parameters are now[type, size]
instead of[size, type]
. TheInlinedFixedVector
now has a default size which means that one can just useInlinedFixedVector
asInlinedFixedVector[Float32]
and the default size is used. -
write_file()
method inBuffer
andNDBuffer
is renamed totofile()
to match the Python naming. -
Mojo will now utilize all available cores across all NUMA sockets on the host machine by default. The prior default behavior was to use all the cores on the first socket.
❌ Removed
- The
math.numerics
module is now private, because its types (FPUtils
andFlushDenormals
) should not be used externally.
🛠️ Fixed
- #532 - Compiler optimizing while True loop away
- #760 - Compilation error: 'hlcf.for.yield' op specifies 0 branch inputs but target expected 1 along control-flow edge from here
- #849 - The
Tensor
type is now initialized with zeros at construction time. - #912 - Invalid load for
__get_address_as_lvalue
. - #916 - Parser crash when
specifying default values for
inout
arguments. - #943 - Mojo hangs if you use continue in the nested loop
- #957 - Parser crash when a function call with variadic arguments of a memory-only type is evaluated at compile time.
- #990 - Fixes rounding issue with floor division with negative numerator.
- #1018 - In some cases the sort function was returning invalid results. This release fixes some of these corner cases.
- #1010 - Initializing tensor in alias declaration results in crash.
- #1110 - The
time.now()
function now returns nanoseconds across all operating systems. - #1115 - cannot load non-register passable type into SSA register.
v0.4.0 for Mac (2023-10-19)
🔥 Legendary
-
Mojo for Mac!
The Mojo SDK now works on macOS (Apple silicon). This is the same version previously released for Linux. Get the latest version of the SDK for your Mac system:
v0.4.0 (2023-10-05)
⭐️ New
-
Mojo now supports default parameter values. For example:
fn foo[a: Int = 3, msg: StringLiteral = "woof"]():
print(msg, a)
fn main():
foo() # prints 'woof 3'
foo[5]() # prints 'woof 5'
foo[7, "meow"]() # prints 'meow 7'fn foo[a: Int = 3, msg: StringLiteral = "woof"]():
print(msg, a)
fn main():
foo() # prints 'woof 3'
foo[5]() # prints 'woof 5'
foo[7, "meow"]() # prints 'meow 7'Inferred parameter values take precedence over defaults:
@value
struct Bar[v: Int]:
pass
fn foo[a: Int = 42, msg: StringLiteral = "quack"](bar: Bar[a]):
print(msg, a)
fn main():
foo(Bar[9]()) # prints 'quack 9'@value
struct Bar[v: Int]:
pass
fn foo[a: Int = 42, msg: StringLiteral = "quack"](bar: Bar[a]):
print(msg, a)
fn main():
foo(Bar[9]()) # prints 'quack 9'Structs also support default parameters:
@value
struct DefaultParams[msg: StringLiteral = "woof"]:
alias message = msg
fn main():
print(DefaultParams[]().message) # prints 'woof'
print(DefaultParams["meow"]().message) # prints 'meow'@value
struct DefaultParams[msg: StringLiteral = "woof"]:
alias message = msg
fn main():
print(DefaultParams[]().message) # prints 'woof'
print(DefaultParams["meow"]().message) # prints 'meow' -
The new
file
module adds basic file I/O support. You can now write:var f = open("my_file.txt", "r")
print(f.read())
f.close()var f = open("my_file.txt", "r")
print(f.read())
f.close()or
with open("my_file.txt", "r") as f:
print(f.read())with open("my_file.txt", "r") as f:
print(f.read()) -
Mojo now allows context managers to support an
__enter__
method without implementing support for an__exit__
method, enabling idioms like this:# This context manager consumes itself and returns it as the value.
fn __enter__(owned self) -> Self:
return self^# This context manager consumes itself and returns it as the value.
fn __enter__(owned self) -> Self:
return self^Here Mojo cannot invoke a noop
__exit__
method because the context manager is consumed by the__enter__
method. This can be used for types (like file descriptors) that are traditionally used withwith
statements, even though Mojo's guaranteed early destruction doesn't require that. -
A very basic version of
pathlib
has been implemented in Mojo. The module will be improved to achieve functional parity with Python in the next few releases. -
The
memory.unsafe
module now contains abitcast
function. This is a low-level operation that enables bitcasting between pointers and scalars. -
The input parameters of a parametric type can now be directly accessed as attribute references on the type or variables of the type. For example:
@value
struct Thing[param: Int]:
pass
fn main():
print(Thing[2].param) # prints '2'
let x = Thing[9]()
print(x.param) # prints '9'@value
struct Thing[param: Int]:
pass
fn main():
print(Thing[2].param) # prints '2'
let x = Thing[9]()
print(x.param) # prints '9'Input parameters on values can even be accessed in parameter contexts. For example:
fn foo[value: Int]():
print(value)
let y = Thing[12]()
alias constant = y.param + 4
foo[constant]() # prints '16'fn foo[value: Int]():
print(value)
let y = Thing[12]()
alias constant = y.param + 4
foo[constant]() # prints '16' -
The Mojo REPL now supports code completion. Press Tab while typing to query potential completion results.
-
Error messages from Python are now exposed in Mojo. For example the following should print
No module named 'my_uninstalled_module'
:fn main():
try:
let my_module = Python.import_module("my_uninstalled_module")
except e:
print(e)fn main():
try:
let my_module = Python.import_module("my_uninstalled_module")
except e:
print(e) -
Error messages can now store dynamic messages. For example, the following should print "Failed on: Hello"
fn foo(x: String) raises:
raise Error("Failed on: " + x)
fn main():
try:
foo("Hello")
except e:
print(e)fn foo(x: String) raises:
raise Error("Failed on: " + x)
fn main():
try:
foo("Hello")
except e:
print(e)
🦋 Changed
-
We have improved and simplified the
parallelize
function. The function now elides some overhead by caching the Mojo parallel runtime. -
The Mojo REPL and Jupyter environments no longer implicitly expose
Python
,PythonObject
, orPointer
. These symbols must now be imported explicitly, for example:from python import Python
from python.object import PythonObject
from memory.unsafe import Pointerfrom python import Python
from python.object import PythonObject
from memory.unsafe import Pointer -
The syntax for specifying attributes with the
__mlir_op
prefix have changed to mimic Python's keyword argument passing syntax. That is,=
should be used instead of:
, e.g.:# Old syntax, now fails.
__mlir_op.`index.bool.constant`[value : __mlir_attr.false]()
# New syntax.
__mlir_op.`index.bool.constant`[value=__mlir_attr.false]()# Old syntax, now fails.
__mlir_op.`index.bool.constant`[value : __mlir_attr.false]()
# New syntax.
__mlir_op.`index.bool.constant`[value=__mlir_attr.false]() -
You can now print the
Error
object directly. Themessage()
method has been removed.
🛠️ Fixed
- #794 - Parser crash when
using the
in
operator. - #936 - The
Int
constructor now accepts otherInt
instances. - #921 - Better error message
when running
mojo
on a module with nomain
function. - #556 - UInt64s are now printed correctly.
- #804 - Emit error instead of crashing when passing variadic arguments of unsupported types.
- #833 - Parser crash when assigning module value.
- #752 - Parser crash when calling async def.
- #711 - The overload resolution
logic now correctly prioritizes instance methods over static methods (if
candidates are an equally good match otherwise), and no longer crashed if a
static method has a
Self
type as its first argument. - #859 - Fix confusing error and
documentation of the
rebind
builtin. - #753 - Direct use of LLVM dialect produces strange errors in the compiler.
- #926 - Fixes an issue that
occurred when a function with a return type of
StringRef
raised an error. When the function raised an error, it incorrectly returned the string value of that error. - #536 - Report More information on python exception.
v0.3.1 (2023-09-28)
Our first-ever patch release of the Mojo SDK is here! Release v0.3.1 includes primarily installation-related fixes. If you’ve had trouble installing the previous versions of the SDK, this release may be for you.
🛠️ Fixed
- #538 - Installation hangs during the testing phase. This issue occurs on machines with a low number of CPU cores, such as free AWS EC2 instances and GitHub Codespaces.
- #590 - Installation fails with a “failed to run python” message.
- #672 - Language server hangs on code completion. Related to #538, this occurs on machines with a low number of CPU cores.
- #913 - In the REPL and Jupyter notebooks, inline comments were being parsed incorrectly.
v0.3.0 (2023-09-21)
There's more Mojo to love in this, the second release of the Mojo SDK! This release includes new features, an API change, and bug fixes.
There's also an updated version of the Mojo extension for VS Code.
⭐️ New
-
Mojo now has partial support for passing keyword arguments to functions and methods. For example the following should work:
fn foo(a: Int, b: Int = 3) -> Int:
return a * b
fn main():
print(foo(6, b=7)) # prints '42'
print(foo(a=6, b=7)) # prints '42'
print(foo(b=7, a=6)) # prints '42'fn foo(a: Int, b: Int = 3) -> Int:
return a * b
fn main():
print(foo(6, b=7)) # prints '42'
print(foo(a=6, b=7)) # prints '42'
print(foo(b=7, a=6)) # prints '42'Parameters can also be inferred from keyword arguments, for example:
fn bar[A: AnyType, B: AnyType](a: A, b: B):
print("Hello 🔥")
fn bar[B: AnyType](a: StringLiteral, b: B):
print(a)
fn main():
bar(1, 2) # prints `Hello 🔥`
bar(b=2, a="Yay!") # prints `Yay!`fn bar[A: AnyType, B: AnyType](a: A, b: B):
print("Hello 🔥")
fn bar[B: AnyType](a: StringLiteral, b: B):
print(a)
fn main():
bar(1, 2) # prints `Hello 🔥`
bar(b=2, a="Yay!") # prints `Yay!`For the time being, the following notable limitations apply:
-
Keyword-only arguments are not supported:
fn baz(*args: Int, b: Int): pass # fails
fn baz(a: Int, *, b: Int): pass # failsfn baz(*args: Int, b: Int): pass # fails
fn baz(a: Int, *, b: Int): pass # fails(Keyword-only arguments are described in PEP 3102.)
-
Variadic keyword arguments are not supported:
fn baz(a: Int, **kwargs: Int): pass # fails
fn baz(a: Int, **kwargs: Int): pass # fails
-
-
Mojo now supports the
@nonmaterializable
decorator. The purpose is to mark data types that should only exist in the parameter domain. To use it, a struct is decorated with@nonmaterializable(TargetType)
. Any time the nonmaterializable type is converted from the parameter domain, it is automatically converted toTargetType
. A nonmaterializable struct should have all of its methods annotated as@always_inline
, and must be computable in the parameter domain. In the following example, theNmStruct
type can be added in the parameter domain, but are converted toHasBool
when materialized.@value
@register_passable("trivial")
struct HasBool:
var x: Bool
fn __init__(x: Bool) -> Self:
return Self {x: x}
@always_inline("nodebug")
fn __init__(nms: NmStruct) -> Self:
return Self {x: True if (nms.x == 77) else False}
@value
@nonmaterializable(HasBool)
@register_passable("trivial")
struct NmStruct:
var x: Int
@always_inline("nodebug")
fn __add__(self: Self, rhs: Self) -> Self:
return NmStruct(self.x + rhs.x)
alias stillNmStruct = NmStruct(1) + NmStruct(2)
# When materializing to a run-time variable, it is automatically converted,
# even without a type annotation.
let convertedToHasBool = stillNmStruct@value
@register_passable("trivial")
struct HasBool:
var x: Bool
fn __init__(x: Bool) -> Self:
return Self {x: x}
@always_inline("nodebug")
fn __init__(nms: NmStruct) -> Self:
return Self {x: True if (nms.x == 77) else False}
@value
@nonmaterializable(HasBool)
@register_passable("trivial")
struct NmStruct:
var x: Int
@always_inline("nodebug")
fn __add__(self: Self, rhs: Self) -> Self:
return NmStruct(self.x + rhs.x)
alias stillNmStruct = NmStruct(1) + NmStruct(2)
# When materializing to a run-time variable, it is automatically converted,
# even without a type annotation.
let convertedToHasBool = stillNmStruct -
Mojo integer literals now produce the
IntLiteral
infinite precision integer type when used in the parameter domain.IntLiteral
is materialized to theInt
type for runtime computation, but intermediate computations at compile time, using supported operators, can now exceed the bit width of theInt
type. -
The Mojo Language Server now supports top-level code completions, enabling completion when typing a reference to a variable, type, etc. This resolves #679.
-
The Mojo REPL now colorizes the resultant variables to help distinguish input expressions from the output variables.
🦋 Changed
-
Mojo allows types to implement two forms of move constructors, one that is invoked when the lifetime of one value ends, and one that is invoked if the compiler cannot prove that. These were previously both named
__moveinit__
, with the following two signatures:fn __moveinit__(inout self, owned existing: Self): ...
fn __moveinit__(inout self, inout existing: Self): ...fn __moveinit__(inout self, owned existing: Self): ...
fn __moveinit__(inout self, inout existing: Self): ...We've changed the second form to get its own name to make it more clear that these are two separate operations: the second has been renamed to
__takeinit__
:fn __moveinit__(inout self, owned existing: Self): ...
fn __takeinit__(inout self, inout existing: Self): ...fn __moveinit__(inout self, owned existing: Self): ...
fn __takeinit__(inout self, inout existing: Self): ...The name is intended to connote that the operation takes the conceptual value from the source (without destroying it) unlike the first one which "moves" a value from one location to another.
For more information, see the Mojo Manual section on move constructors.
-
The Error type in Mojo has changed. Instead of extracting the error message using
error.value
you will now extract the error message usingerror.message()
.
🛠️ Fixed
- #503 - Improve error message
for failure lowering
kgen.param.constant
. - #554 - Alias of static tuple fails to expand.
- #500 - Call expansion failed due to verifier error.
- #422 - Incorrect comment detection in multiline strings.
- #729 - Improve messaging on how to exit the REPL.
- #756 - Fix initialization errors of the VS Code extension.
- #575 - Build LLDB/REPL with libedit for a nicer editing experience in the terminal.
v0.2.1 (2023-09-07)
The first versioned release of Mojo! 🔥
All earlier releases were considered version 0.1.
🔥 Legendary
-
First release of the Mojo SDK!
You can now develop with Mojo locally. The Mojo SDK is currently available for Ubuntu Linux systems, and support for Windows and macOS is coming soon. You can still develop from a Windows or Mac computer using a container or remote Linux system.
The Mojo SDK includes the Mojo standard library and the Mojo command-line interface (CLI), which allows you to run, compile, and package Mojo code. It also provides a REPL programming environment.
-
First release of the Mojo extension for VS Code.
This provides essential Mojo language features in Visual Studio Code, such as code completion, code quick fixes, docs tooltips, and more. Even when developing on a remote system, using VS Code with this extension provides a native-like IDE experience.
⭐️ New
-
A new
clobber_memory
function has been added to thebenchmark
module. The clobber memory function tells the system to flush all memory operations at the specified program point. This allows you to benchmark operations without the compiler reordering memory operations. -
A new
keep
function has been added to thebenchmark
module. Thekeep
function tries to tell the compiler not to optimize the variable away if not used. This allows you to avoid compiler's dead code elimination mechanism, with a low footprint side effect. -
New
shift_right
andshift_left
functions have been added to thesimd
module. They shift the elements in a SIMD vector right/left, filling elements with zeros as needed. -
A new
cumsum
function has been added to thereduction
module that computes the cumulative sum (also known as scan) of input elements. -
Mojo Jupyter kernel now supports code completion.
🦋 Changed
- Extends
rotate_bits_left
,rotate_left
,rotate_bits_right
, androtate_right
to operate on Int values. The ordering of parameters has also been changed to enable type inference. Now it's possible to writerotate_right[shift_val](simd_val)
and have thedtype
andsimd_width
inferred from the argument. This addresses Issue #528.
🛠️ Fixed
-
Fixed a bug causing the parser to crash when the
with
statement was written without a colon. This addresses Issue #529. -
Incorrect imports no longer crash when there are other errors at the top level of a module. This fixes Issue #531.
August 2023
2023-08-24
- Fixed issue where the
with expr as x
statement withinfn
behaved as if it were in adef
, bindingx
with function scope instead of using lexical scope.
⭐️ New
-
Major refactoring of the standard library to enable packaging and better import ergonomics:
- The packages are built as binaries to improve startup speed.
- Package and module names are now lowercase to align with the Python style.
- Modules have been moved to better reflect the purpose of the underlying
functions (e.g.
Pointer
is now within theunsafe
module in thememory
package). - The following modules are now included as built-ins:
SIMD
,DType
,IO
,Object
, andString
. This means it's no longer necessary to explicitly import these modules. Instead, these modules will be implicitly imported for the user. Private methods within the module are still accessible using thebuiltin.module_name._private_method
import syntax. - New
math
package has been added to contain thebit
,math
,numerics
, andpolynomial
modules. The contents of themath.math
module are re-exported into themath
package.
-
Mojo now supports using memory-only types in parameter expressions and as function or type parameters:
@value
struct IntPair:
var first: Int
var second: Int
fn add_them[value: IntPair]() -> Int:
return value.first + value.second
fn main():
print(add_them[IntPair(1, 2)]()) # prints '3'@value
struct IntPair:
var first: Int
var second: Int
fn add_them[value: IntPair]() -> Int:
return value.first + value.second
fn main():
print(add_them[IntPair(1, 2)]()) # prints '3' -
In addition, Mojo supports evaluating code that uses heap-allocated memory at compile-time and materializing compile-time values with heap-allocated memory into dynamic values:
fn fillVector(lowerBound: Int, upperBound: Int, step: Int) -> DynamicVector[Int]:
var result = DynamicVector[Int]()
for i in range(lowerBound, upperBound, step):
result.push_back(i)
return result
fn main():
alias values = fillVector(5, 23, 7)
for i in range(0, values.__len__()):
print(values[i]) # prints '5', '12', and then '19'fn fillVector(lowerBound: Int, upperBound: Int, step: Int) -> DynamicVector[Int]:
var result = DynamicVector[Int]()
for i in range(lowerBound, upperBound, step):
result.push_back(i)
return result
fn main():
alias values = fillVector(5, 23, 7)
for i in range(0, values.__len__()):
print(values[i]) # prints '5', '12', and then '19'
🦋 Changed
-
def main():
, without the explicitNone
type, can now be used to define the entry point to a Mojo program. -
The
assert_param
function has been renamed toconstrained
and is now a built-in function. -
The
print
function now works onComplex
values.
🛠️ Fixed
- Fixed issues with print formatting for
DType.uint16
andDType.int16
. - Issue #499 - Two new
rotate_right
androtate_left
functions have been added to the SIMD module. - Issue #429 - You can now
construct a
Bool
from aSIMD
type whose element-type isDType.bool
. - Issue #350 - Confusing Matrix implementation
- Issue #349 - Missing load_tr in struct Matrix
- Issue #501 - Missing syntax error messages in Python expressions.
2023-08-09
🦋 Changed
-
The
ref
andmutref
identifiers are now treated as keywords, which means they cannot be used as variable, attribute, or function names. These keywords are used by the "lifetimes" features, which is still in development. We can consider renaming these (as well as other related keywords) when the development work gels, support is enabled in public Mojo builds, and when we have experience using them. -
The argument handling in
def
functions has changed: previously, they had special behavior that involved mutable copies in the callee. Now, we have a simple rule, which is thatdef
argument default to theowned
convention (fn
arguments still default to theborrowed
convention).This change is mostly an internal cleanup and simplification of the compiler and argument model, but does enable one niche use-case: you can now pass non-copyable types to
def
arguments by transferring ownership of a value into thedef
call. Before, that would not be possible because the copy was made on the callee side, not the caller's side. This also allows the explicit use of theborrowed
keyword with adef
that wants to opt-in to that behavior.
2023-08-03
⭐️ New
-
A new
Tensor
type has been introduced. This tensor type manages its own data (unlikeNDBuffer
andBuffer
which are just views). Therefore, the tensor type performs its own allocation and free. Here is a simple example of using the tensor type to represent an RGB image and convert it to grayscale:from tensor import Tensor, TensorShape
from utils.index import Index
from random import rand
let height = 256
let width = 256
let channels = 3
# Create the tensor of dimensions height, width, channels and fill with
# random value.
let image = rand[DType.float32](height, width, channels)
# Declare the grayscale image.
var gray_scale_image = Tensor[DType.float32](height, width)
# Perform the RGB to grayscale transform.
for y in range(height):
for x in range(width):
let r = image[y, x, 0]
let g = image[y, x, 1]
let b = image[y, x, 2]
gray_scale_image[Index(y, x)] = 0.299 * r + 0.587 * g + 0.114 * bfrom tensor import Tensor, TensorShape
from utils.index import Index
from random import rand
let height = 256
let width = 256
let channels = 3
# Create the tensor of dimensions height, width, channels and fill with
# random value.
let image = rand[DType.float32](height, width, channels)
# Declare the grayscale image.
var gray_scale_image = Tensor[DType.float32](height, width)
# Perform the RGB to grayscale transform.
for y in range(height):
for x in range(width):
let r = image[y, x, 0]
let g = image[y, x, 1]
let b = image[y, x, 2]
gray_scale_image[Index(y, x)] = 0.299 * r + 0.587 * g + 0.114 * b
🛠️ Fixed
- Issue #53 -
Int
now implements true division with the/
operator. Similar to Python, this returns a 64-bit floating point number. The corresponding in-place operator,/=
, has the same semantics as//=
.
July 2023
2023-07-26
⭐️ New
-
Types that define both
__getitem__
and__setitem__
(i.e. where sub-scripting instances creates computed LValues) can now be indexed in parameter expressions. -
Unroll decorator for loops with constant bounds and steps:
@unroll
: Fully unroll a loop.@unroll(n)
: Unroll a loop by factor of n, wheren
is a positive integer.- Unroll decorator requires loop bounds and iteration step to be compiler time constant value, otherwise unrolling will fail with compilation error. This also doesn't make loop induction variable a parameter.
# Fully unroll the loop.
@unroll
for i in range(5):
print(i)
# Unroll the loop by a factor of 4 (with remainder iterations of 2).
@unroll(4)
for i in range(10):
print(i)# Fully unroll the loop.
@unroll
for i in range(5):
print(i)
# Unroll the loop by a factor of 4 (with remainder iterations of 2).
@unroll(4)
for i in range(10):
print(i) -
The Mojo REPL now prints the values of variables defined in the REPL. There is full support for scalars and structs. Non-scalar SIMD vectors are not supported at this time.
🛠️ Fixed
-
Issue #437 - Range can now be instantiated with a PythonObject.
-
Issue #288 - Python strings can now be safely copied.
2023-07-20
⭐️ New
-
Mojo now includes a
Limits
module, which contains functions to get the max and min values representable by a type, as requested in Issue #51. The following functions moved fromMath
toLimits
:inf()
,neginf()
,isinf()
,isfinite()
. -
Mojo decorators are now distinguished between "signature" and "body" decorators and are ordered. Signature decorators, like
@register_passable
and@parameter
, modify the type of declaration before the body is parsed. Body decorators, like@value
, modify the body of declaration after it is fully parsed. Due to ordering, a signature decorator cannot be applied after a body decorator. That means the following is now invalid:@register_passable # error: cannot apply signature decorator after a body one!
@value
struct Foo:
pass@register_passable # error: cannot apply signature decorator after a body one!
@value
struct Foo:
pass -
Global variables can now be exported in Mojo compiled archives, using the
@export
decorator. Exported global variables are public symbols in compiled archives and use the variable name as its linkage name, by default. A custom linkage name can be specified with@export("new_name")
. This does not affect variable names in Mojo code. -
Mojo now supports packages! A Mojo package is defined by placing an
__init__.mojo
or__init__.🔥
within a directory. Other files in the same directory form modules within the package (this works exactly like it does in Python). Example:main.🔥
my_package/
__init__.🔥
module.🔥
my_other_package/
__init__.🔥
stuff.🔥main.🔥
my_package/
__init__.🔥
module.🔥
my_other_package/
__init__.🔥
stuff.🔥# main.🔥
from my_package.module import some_function
from my_package.my_other_package.stuff import SomeType
fn main():
var x: SomeType = some_function()# main.🔥
from my_package.module import some_function
from my_package.my_other_package.stuff import SomeType
fn main():
var x: SomeType = some_function() -
Mojo now supports direct module and package imports! Modules and packages can be imported and bound to names. Module and package elements, like functions, types, global variables, and other modules, can be accessed using attribute references, like
my_module.foo
. Note that modules lack runtime representations, meaning module references cannot be instantiated.import builtin.io as io
import SIMD
io.print("hello world")
var x: SIMD.Float32 = 1.2import builtin.io as io
import SIMD
io.print("hello world")
var x: SIMD.Float32 = 1.2
🦋 Changed
-
Reverted the feature from 2023-02-13 that allowed unqualified struct members. Use the
Self
keyword to conveniently access struct members with bound parameters instead. This was required to fix Issue #260. -
Updated the RayTracing notebook: added step 5 to create specular lighting for more realistic images and step 6 to add a background image.
🛠️ Fixed
- Issue #260 - Definitions inside structs no longer shadow definitions outside of struct definitions.
2023-07-12
⭐️ New
-
Mojo now has support for global variables! This enables
var
andlet
declaration at the top-level scope in Mojo files. Global variable initializers are run when code modules are loaded by the platform according to the order of dependencies between global variables, and their destructors are called in the reverse order. -
The Mojo programming manual is now written as a Jupyter notebook, and available in its entirety in the Mojo Playground (
programming-manual.ipynb
). (Previously,HelloMojo.ipynb
included most of the same material, but it was not up-to-date.) -
As a result, we've also re-written
HelloMojo.ipynb
to be much shorter and provide a more gentle first-user experience. -
Coroutine
module documentation is now available. Coroutines form the basis of Mojo's support for asynchronous execution. Calls toasync fn
s can be stored into aCoroutine
, from which they can be resumed, awaited upon, and have their results retrieved upon completion.
🦋 Changed
simd_bit_width
in theTargetInfo
module has been renamed tosimdbitwidth
to better align withsimdwidthof
,bitwidthof
, etc.
🛠️ Fixed
-
The walrus operator now works in if/while statements without parentheses, e.g.
if x := function():
. -
Issue #428 - The
FloatLiteral
andSIMD
types now support conversion toInt
via theto_int
or__int__
method calls. The behavior matches that of Python, which rounds towards zero.
2023-07-05
⭐️ New
- Tuple expressions now work without parentheses. For example,
a, b = b, a
works as you'd expect in Python. - Chained assignments (e.g.
a = b = 42
) and the walrus operator (e.g.some_function(b := 17)
) are now supported.
🦋 Changed
-
The
simd_width
anddtype_simd_width
functions in theTargetInfo
module have been renamed tosimdwidthof
. -
The
dtype_
prefix has been dropped fromalignof
,sizeof
, andbitwidthof
. You can now use these functions (e.g.alignof
) with any argument type, includingDType
. -
The
inf
,neginf
,nan
,isinf
,isfinite
, andisnan
functions were moved from theNumerics
module to theMath
module, to better align with Python's library structure.
🛠️ Fixed
-
Issue #253 - Issue when accessing a struct member alias without providing parameters.
-
Issue #404 - The docs now use
snake_case
for variable names, which more closely conforms to Python's style. -
Issue #379 - Tuple limitations have been addressed and multiple return values are now supported, even without parentheses.
-
Issue #347 - Tuples no longer require parentheses.
-
Issue #320 - Python objects are now traversable via
for
loops.
June 2023
2023-06-29
⭐️ New
- You can now share
.ipynb
notebook files in Mojo Playground. Just save a file in theshared
directory, and then right-click the file and select Copy Sharable link. To open a shared notebook, you must already have access to Mojo Playground; when you open a shared notebook, click Import at the top of the notebook to save your own copy. For more details about this feature, see the instructions inside thehelp
directory, in the Mojo Playground file browser.
🦋 Changed
- The
unroll2()
andunroll3()
functions in theFunctional
module have been renamed to overload theunroll()
function. These functions unroll 2D and 3D loops andunroll()
can determine the intent based on the number of input parameters.
🛠️ Fixed
-
Issue #229 - Issue when throwing an exception from
__init__
before all fields are initialized. -
Issue #74 - Struct definition with recursive reference crashes.
-
Issue #285 - The
TargetInfo
module now includesis_little_endian()
andis_big_endian()
to check if the target host uses either little or big endian. -
Issue #254 - Parameter name shadowing in nested scopes is now handled correctly.
2023-06-21
⭐️ New
-
Added support for overloading on parameter signature. For example, it is now possible to write the following:
fn foo[a: Int](x: Int):
pass
fn foo[a: Int, b: Int](x: Int):
passfn foo[a: Int](x: Int):
pass
fn foo[a: Int, b: Int](x: Int):
passFor details on the overload resolution logic, see the Mojo Manual section on parameters.
-
A new
cost_of()
function has been added toAutotune
. This meta-function must be invoked at compile time, and it returns the number of MLIR operations in a function (at a certain stage in compilation), which can be used to build basic heuristics in higher-order generators.from autotune import cost_of
fn generator[f: fn(Int) -> Int]() -> Int:
@parameter
if cost_of[fn(Int) -> Int, f]() < 10:
return f()
else:
# Do something else for slower functions...from autotune import cost_of
fn generator[f: fn(Int) -> Int]() -> Int:
@parameter
if cost_of[fn(Int) -> Int, f]() < 10:
return f()
else:
# Do something else for slower functions... -
Added a new example notebook with a basic Ray Tracing algorithm.
🦋 Changed
- The
constrained_msg()
in theAssert
module has been renamed toconstrained()
.
🛠️ Fixed
-
Overloads marked with
@adaptive
now correctly handle signatures that differ only in declared parameter names, e.g. the following now works correctly:@adaptive
fn foobar[w: Int, T: DType]() -> SIMD[T, w]: ...
@adaptive
fn foobar[w: Int, S: DType]() -> SIMD[S, w]: ...@adaptive
fn foobar[w: Int, T: DType]() -> SIMD[T, w]: ...
@adaptive
fn foobar[w: Int, S: DType]() -> SIMD[S, w]: ... -
Issue #219 - Issue when redefining a function and a struct defined in the same cell.
-
Issue #355 - The loop order in the Matmul notebook for Python and naive mojo have been reordered for consistency. The loop order now follows (M, K, N) ordering.
-
Issue #309 - Use snake case naming within the testing package and move the asserts out of the TestSuite struct.
2023-06-14
⭐️ New
-
Tuple type syntax is now supported, e.g. the following works:
fn return_tuple() -> (Int, Int):
return (1, 2)fn return_tuple() -> (Int, Int):
return (1, 2)
🦋 Changed
- The
TupleLiteral
type was renamed to justTuple
, e.g.Tuple[Int, Float]
.
🛠️ Fixed
- Issue #354 - Returning a tuple doesn't work even with parens.
- Issue #365 - Copy-paste error
in
FloatLiteral
docs. - Issue #357 - Crash when missing input parameter to variadic parameter struct member function.
2023-06-07
⭐️ New
- Tuple syntax now works on the left-hand side of assignments (in "lvalue"
positions), enabling things like
(a, b) = (b, a)
. There are several caveats: the element types must exactly match (no implicit conversions), this only works with values ofTupleLiteral
type (notably, it will not work withPythonObject
yet) and parentheses are required for tuple syntax.
❌ Removed
- Mojo Playground no longer includes the following Python packages (due to size,
compute costs, and
environment complications):
torch
,tensorflow
,keras
,transformers
.