v24.6 (2024-12-17)
✨ Highlights
Here's a brief summary of some of the major changes in this release, with more detailed information in the following sections:
-
The
inoutandborrowedargument conventions have been renamed tomutandread, respectively. A newoutconvention has been added for theselfargument in constructors and for named results. See Language changes for details. -
Lifetimeand related types in the standard library have been renamed toOriginto better clarify that parameters of this type indicate where a reference is derived from, not the more complicated notion of where a variable is initialized and destroyed. As a consequence the__lifetime_of()operator is now named__origin_of().There are also a number of other origin-related improvements in this release, including being able to specify a union of origins by listing multiple values in the
__origin_of()operator or inside thereforigin specifier (ref [a, b]). For details, see Language changes.For background information and rationale on the name change see the proposal. For more information on origins, see Lifetimes, origins and references in the Mojo Manual.
-
Implicit conversions are now opt-in using the
@implicitdecorator. See Language changes for details. -
The standard library has added several new types, including
Deque(a double-ended queue) andOwnedPointer(safe, single-owner, non-nullable smart pointer). See Standard library changes for details. -
The VS Code extension now supports setting data breakpoints and function breakpoints, and the Mojo LLDB debugger supports symbol breakpoints, such as
b mainorb my_module::main. -
We've made a number of improvement to how information is displayed in error messages, LSP, and generated API documentation. For details, see Tooling changes.
-
And we've added a number of new docs, including a brand new Mojo tutorial, new pages on operators and expressions, error handling, and pointers, and many smaller additions and improvements.
Language changes
-
Argument convention changes:
-
The
inoutandborrowedargument conventions have been renamed tomut(for "mutate") andread, respectively. These verbs reflect what the callee can do to the argument value passed in by the caller, without requiring the programmer to know about advanced features like references.For information on Mojo's argument conventions, see Argument conventions in the Mojo Manual.
-
The argument convention for the
selfargument in the__init__(),__copyinit__(), and__moveinit__()methods has been changed frominouttoout, reflecting that a constructor method initializes itsselfvalue without reading from it. This also enables spelling the type of an initializer correctly, which was not supported before:struct Foo:
fn __init__(out self): pass
fn test():
# This works now
var fnPtr : fn(out x: Foo)->None = Foo.__init__
var someFoo : Foo
fnPtr(someFoo) # initializes someFoo.The previous
fn __init__(inout self)syntax is still supported in this release of Mojo, but will be removed in the future. Please migrate to the new syntax. -
Similarly, the spelling of named results has switched to use
outsyntax instead of-> T as name. Functions may have at most one named result or return type specified with the usual->syntax.outarguments may occur anywhere in the argument list, but are typically last (except for__init__methods, where they are typically first).# This function has type "fn() -> String"
fn example(out result: String):
result = "foo"The parser still accepts the old syntax as a synonym for this, but that will eventually be deprecated and removed.
This was discussed extensively in a public proposal. For more information, see Named results in the Mojo Manual.
-
-
Single argument constructors now require the
@implicitdecorator to allow for implicit conversions. Previously you could define an__init__that takes a single argument:struct Foo:
var value: Int
fn __init__(out self, value: Int):
self.value = valueAnd this would allow you to pass an
Intin the position of aFoo:fn func(foo: Foo):
print("implicitly converted Int to Foo:", foo.value)
fn main():
func(Int(42))This can result in complicated errors that are difficult to debug. By default this implicit behavior is now turned off, so you have to explicitly construct
Foo:fn main():
func(Foo(42))You can still opt into implicit conversions by adding the
@implicitdecorator. For example, to enable implicit conversions fromInttoFoo:struct Foo:
var value: Int
@implicit
fn __init__(out self, value: Int):
self.value = valueFor more information see Constructors and implicit conversion in the Mojo Manual.
-
Origin-related changes:
-
The
AnyLifetimetype (useful for declaring origin types as parameters) has has been renamed toOriginand the__lifetime_of()operator renamed to__origin_of(). -
Originis now a complete wrapper around the MLIR origin type.-
The
Origin.typealias has been renamed to_mlir_origin. In parameter lists, you can now write justOrigin[..], instead ofOrigin[..].type. -
ImmutableOriginandMutableOriginare now, respectively, just aliases forOrigin[False]andOrigin[True]. -
Originstruct values are now supported in the origin specifier of aref [..]argument. -
Added
Origin.cast_fromfor casting the mutability of an origin value.
-
-
refarguments and results now allow for providing a memory value directly in the origin specifier, rather than requiring the use of__origin_of(). It is still fine to use__origin_of()explicitly though, and this is required when specifying origins for parameters (e.g. to thePointertype). For example, this is now valid without__origin_of():fn return_ref(a: String) -> ref [a] String:
return a -
Various improvements to origin handling and syntax have landed, including support for the ternary operator and allowing multiple arguments in a
refspecifier (which are implicitly unions). This enables expression of simple algorithms cleanly:fn my_min[T: Comparable](ref a: T, ref b: T) -> ref [a, b] T:
return a if a < b else bIt is also nice that
my_minautomatically and implicitly propagates the mutability of its arguments, so things likemy_min(str1, str2) += "foo"is valid. -
reffunction arguments without an origin clause are now treated asref [_], which is more syntactically convenient and consistent:fn takes_and_return_ref(ref a: String) -> ref [a] String:
return a -
The
__type_of(x)and__origin_of(x)operators are much more general now: they allow arbitrary expressions inside of them, allow referring to dynamic values in parameter contexts, and even allow referring to raising functions in non-raising contexts. These operations never evaluate their expression, so any side effects that occur in the expression are never evaluated at runtime, eliminating concerns about__type_of(expensive())being a problem. -
The destructor insertion logic in Mojo is now aware that types that take an
MutableAnyOriginorImmutableAnyOriginas part of their signature could potentially access any live value that destructor insertion is tracking, eliminating a significant usability issue with unsafe APIs likeUnsafePointer. Consider a typical example working with strings before this change:var str = String(...)
var ptr = str.unsafe_ptr()
some_low_level_api(ptr)
_ = str^ # OLD HACK: Explicitly keep string alive until here!The
_ = str^pattern was formerly required because the Mojo compiler has no idea what "ptr" might reference. As a consequence, it had no idea thatsome_low_level_api()might accessstrand therefore thought it was ok to destroy theStringbefore the call - this is why the explicit lifetime extension was required.Mojo now knows that
UnsafePointermay access theMutableAnyOriginorigin, and now assumes that any API that uses that origin could use live values. In this case, it assumes thatsome_low_level_api()might accessstrand because it might be using it, it cannot destroystruntil after the call. The consequence of this is that the old hack is no longer needed for these cases! -
Function types now accept an origin set parameter. This parameter represents the origins of values captured by a parameter closure. The compiler automatically tags parameter closures with the right set of origins. This enables lifetimes and parameter closures to correctly compose.
fn call_it[f: fn() capturing [_] -> None]():
f()
fn test():
var msg = String("hello world")
@parameter
fn say_hi():
print(msg)
call_it[say_hi]()
# no longer need to write `_ = msg^`!!Note that this only works for higher-order functions which have explicitly added
[_]as the capture origins. By default, the compiler still assumes acapturingclosure does not reference any origins. This will soon change.
-
-
Infer-only parameters may now be explicitly bound with keywords, enabling some important patterns in the standard library:
struct StringSlice[is_mutable: Bool, //, origin: Origin[is_mutable]]: ...
alias ImmStringSlice = StringSlice[is_mutable=False]
# This auto-parameterizes on the origin, but constrains it to being an
# immutable slice instead of a potentially mutable one.
fn take_imm_slice(a: ImmStringSlice): ... -
The flag for turning on asserts has changed, e.g. to enable all checks:
mojo -D ASSERT=all main.mojoThe levels are:
none: all assertions offwarn: print assertion errors e.g. for multithreaded tests (previously-D ASSERT_WARNING)safe: the default mode for standard CPU safety assertionsall: turn on all assertions (previously-D MOJO_ENABLE_ASSERTIONS)
You can now also pass
Stringableargs to format a message, which will have no runtime penalty or IR bloat cost when assertions are off. Previously you had to:x = -1
debug_assert(
x > 0, String.format_sequence(“expected x to be more than 0 but got: ”, x)
)Which can't be optimized away by the compiler in release builds, you can now pass multiple args for a formatted message at no runtime cost:
debug_assert(x > 0, “expected x to be more than 0 but got: ”, x) -
Automatic parameterization of parameters is now supported. Specifying a parameterized type with unbound parameters causes them to be implicitly added to the function signature as infer-only parameters.
fn foo[value: SIMD[DType.int32, _]]():
pass
# Equivalent to
fn foo[size: Int, //, value: SIMD[DType.int32, size]]():
pass -
Mojo can now interpret simple LLVM intrinsics in parameter expressions, enabling things like
count_leading_zerosto work at compile time: Issue #933. -
Introduced the
@explicit_destroyannotation, the__disable_delkeyword, theUnknownDestructibilitytrait, and theImplicitlyDestructiblekeyword, for the experimental explicitly destroyed types feature. -
Added associated types; we can now have aliases like
alias T: AnyType,alias N: Int, etc. in a trait, and then specify them in structs that conform to that trait. For more information, see Associated aliases for generics.
Standard library changes
-
Introduced a new
Deque(double-ended queue) collection type, based on a dynamically resizing circular buffer for efficient O(1) additions and removals at both ends as well as O(1) direct access to all elements.The
Dequesupports the full Pythoncollections.dequeAPI, ensuring that all expected deque operations perform as in Python.Enhancements to the standard Python API include
peek()andpeekleft()methods for non-destructive access to the last and first elements, and advanced constructor options (capacity,min_capacity, andshrink) for customizing memory allocation and performance. These options allow for optimized memory usage and reduced buffer reallocations, providing flexibility based on application requirements. -
The
Formatterstruct has been replaced with aWritertrait to enable buffered IO, increasing print and file writing perf to the same speed as C. It's now more general purpose and can write anySpan[Byte]. To align with this theFormattabletrait is now namedWritable, and theString.format_sequence()static method to initialize a newStringhas been renamed toString.write(). Here's an example of using all of the changes:from memory import Span
@value
struct NewString(Writer, Writable):
var s: String
# Writer requirement to write a Span of Bytes
fn write_bytes(inout self, bytes: Span[Byte, _]):
self.s._iadd[False](bytes)
# Writer requirement to take multiple args
fn write[*Ts: Writable](inout self, *args: *Ts):
@parameter
fn write_arg[T: Writable](arg: T):
arg.write_to(self)
args.each[write_arg]()
# Also make it Writable to allow `print` to write the inner String
fn write_to[W: Writer](self, inout writer: W):
writer.write(self.s)
@value
struct Point(Writable):
var x: Int
var y: Int
# Pass multiple args to the Writer. The Int and StringLiteral types call
# `writer.write_bytes` in their own `write_to` implementations.
fn write_to[W: Writer](self, inout writer: W):
writer.write("Point(", self.x, ", ", self.y, ")")
# Enable conversion to a String using `str(point)`
fn __str__(self) -> String:
return String.write(self)
fn main():
var point = Point(1, 2)
var new_string = NewString(str(point))
new_string.write("\n", Point(3, 4))
print(new_string)Point(1, 2)
Point(3, 4) -
The
TypeIdentifiabletrait has been removed in favor of the newget_type_nameutility in thecompile.reflectionmodule. -
Python interop changes:
-
Introduced
TypedPythonObjectas a light-weight way to annotatePythonObjectvalues with static type information. This design will likely evolve and change significantly.- Added
TypedPythonObject[Tuple].__getitem__()for accessing the elements of a Python tuple.
- Added
-
Added
Python.add_object(), to add a namedPythonObjectvalue to a Python 'module' object instance. -
Added
Python.unsafe_get_python_exception(), as an efficient low-level utility to get the MojoErrorequivalent of the current CPython error state. -
Add
PythonObject.from_borrowed_ptr(), to simplify the construction ofPythonObjectvalues from CPython 'borrowed reference' pointers.The existing
PythonObject.__init__(PyObjectPtr)should continue to be used for the more common case of constructing aPythonObjectfrom a 'strong reference' pointer. -
Support for multi-dimensional indexing and slicing for
PythonObject(PR #3549, PR #3583).var np = Python.import_module("numpy")
var a = np.array(PythonObject([1,2,3,4,5,6])).reshape(2,3)
print((a[0, 1])) # 2
print((a[1][::-1])) # [6 5 4]Note that the syntax,
a[1, ::-1], is currently not supported. -
Added
PythonObject.__contains__(). (PR #3101)Example usage:
x = PythonObject([1,2,3])
if 1 in x:
print("1 in x")
-
-
Pointer related changes:
-
The
UnsafePointertype now has anoriginparameter that can be used when theUnsafePointerpoints to a value with a known origin. This origin is propagated through theptr[]indirection operation. This parameter and otherUnsafePointerparameters (other than the type) are now keyword-only. -
You can now index into
UnsafePointerusingSIMDscalar integral types:p = UnsafePointer[Int].alloc(1)
i = UInt8(1)
p[i] = 42
print(p[i]) -
Added a new
OwnedPointertype as a safe, single-owner, non-nullable smart pointer with similar semantics to Rust'sBox<>and C++'sstd::unique_ptr. (PR #3524) -
Archas been renamed toArcPointer, for consistency withOwnedPointer. -
ArcPointernow implementsIdentifiable, and can be compared for pointer equivalence usinga is b. -
The
Referencetype has been renamed toPointer: a memory safe complement toUnsafePointer. This change is motivated by the fact thatPointeris assignable and requires an explicit dereference withptr[]. Renaming toPointerclarifies that "references" meansrefarguments and results, and gives us a model that is more similar to what the C++ community would expect.For an overview of Mojo's pointer types, see the new Intro to pointers page in the Mojo Manual.
-
A new
as_noalias_ptr()method as been added toUnsafePointer. This method specifies to the compiler that the resultant pointer is a distinct identifiable object that does not alias any other memory in the local scope.
-
-
Added the
FloatableandFloatableRaisingtraits to denote types that can be converted to aFloat64value using the builtinfloatfunction. MadeSIMDandFloatLiteralconform to theFloatabletrait. (PR #3163)fn foo[F: Floatable](v: F):
...
var f = float(Int32(45)) -
The
rebind()standard library function now works with memory-only types in addition to@register_passable("trivial")ones, without requiring a copy. For more information, see Therebind()builtin in the Mojo Manual. -
Introduced the
random.shuffle()function for randomizing the elements of aList. (PR #3327)Example:
from random import shuffle
var l = List[Int](1, 2, 3, 4, 5)
shuffle(l) -
The
Dict.__getitem__()method now returns a reference instead of a copy of the value (or raises). This improves the performance of common code that usesDictby allowing borrows from theDictelements. -
Slice.stepis now anOptional[Int], matching the optionality ofslice.stepin Python. (PR #3160) -
There is now a
Bytealias to better express intent when working with a pack of bits. (PR #3670). -
Expanded
os.pathwith new functions: -
Added a
reserve()method and new constructor to theStringstruct to allocate additional capacity. (PR #3755). -
A new
StringLiteral.get[some_stringable]()method is available. It allows forming a runtime-constantStringLiteralfrom a compile-time-dynamicStringablevalue. -
Spanhas moved from theutilsmodule to thememorymodule. -
Spannow implements__reversed__(). This means that one can get a reverse iterator over aSpanusingreversed(my_span). Users should currently prefer this method overmy_span[::-1]. -
A new
AsBytestrait has been added to enable taking aSpan[Byte]from any type that implementsas_bytes().String.as_bytes()andString.as_bytes_slice()have been consolidated underString.as_bytes()to return aSpan[Byte]. If you require a copy, you can convert theSpanto aListwithList(my_string.as_bytes()). -
StringSlicenow implementsstrip(),rstrip(), andlstrip(). -
StringRefnow implementssplit()which can be used to split aStringRefinto aList[StringRef]by a delimiter. (PR #2705) -
StringRefis now representable sorepr(StringRef("hello"))will returnStringRef('hello'). -
More things have been removed from the auto-exported set of entities in the
preludemodule from the Mojo standard library:UnsafePointerhas been removed. Please explicitly import it viafrom memory import UnsafePointer.StringRefhas been removed. Please explicitly import it viafrom utils import StringRef.
-
Restored implicit copyability of
TupleandListLiteral. -
The aliases for C foreign function interface (FFI) have been renamed:
C_int->c_int,C_long->c_longand so on. -
Float32andFloat64are now printed and converted to strings with roundtrip guarantee and shortest representation:Value Old New
Float64(0.3) 0.29999999999999999 0.3
Float32(0.3) 0.30000001192092896 0.3
Float64(0.0001) 0.0001 0.0001
Float32(0.0001) 9.9999997473787516e-05 0.0001
Float64(-0.00001) -1.0000000000000001e-05 -1e-05
Float32(-0.00001) -9.9999997473787516e-06 -1e-05
Float32(0.00001234) 1.2339999557298142e-05 1.234e-05
Float32(-0.00000123456) -1.2345600453045336e-06 -1.23456e-06
Float64(1.1234567e-320) 1.1235052786429946e-320 1.1235e-320
Float64(1.234 * 10**16) 12340000000000000.0 1.234e+16 -
The
StaticIntTupledata structure in theutilspackage has been renamed toIndexList. The data structure now allows one to specify the index bitwidth of the elements along with whether the underlying indices are signed or unsigned. -
Added
DLHandle.get_symbol(), for getting a pointer to a symbol in a dynamic library. This is more general purpose than the existing methods for getting function pointers.
Tooling changes
-
The VS Code Mojo Debugger now has a
buildArgsJSON debug configuration setting that can be used in conjunction withmojoFileto define the build arguments when compiling the Mojo file. -
The VS Code extension now supports a
Configure Build and Run Argscommand that helps set the build and run args for actions fileRun Mojo FileandDebug Mojo File. A corresponding button appears inRun and Debugselector in the top right corner of a Mojo File. -
The VS Code extension now has the
mojo.run.focusOnTerminalAfterLaunchsetting, which controls whether to focus on the terminal used by theMojo: Run Mojo Filecommand or on the editor after launch. Issue #3532. -
The VS Code extension now has the
mojo.SDK.additionalSDKssetting, which allows the user to provide a list of MAX SDKs that the extension can use when determining a default SDK to use. The user can select the default SDK to use with theMojo: Select the default MAX SDKcommand. -
The VS Code extension now supports setting data breakpoints as well as function breakpoints.
-
The Mojo LLDB debugger now supports symbol breakpoints, for example,
b mainorb my_module::main. -
Error messages that include type names no longer include inferred or defaulted parameters when they aren't needed. For example, previously Mojo complained about things like:
... cannot be converted from 'UnsafePointer[UInt, 0, _default_alignment::AnyType](), MutableAnyOrigin]' to 'UnsafePointer[Int, 0, _default_alignment[::AnyType](), MutableAnyOrigin]'it now complains more helpfully that:
... cannot be converted from 'UnsafePointer[UInt]' to 'UnsafePointer[Int]' -
Tooling now prints the origins of
refarguments and results correctly, and printsselfinstead ofself: Selfin methods. -
The Mojo Language Server and generated documentation now print parametric result types correctly, e.g. showing
SIMD[type, simd_width]instead ofSIMD[$0, $1]. -
Generated API documentation now shows the signatures for structs, and identifies
@register_passableand@register_passable("trivial")types. -
The VS Code extension now allows cancelling the installation of its private MAX SDK.
-
The VS Code extension now opens the Run and Debug tab automatically whenever a debug session starts.
-
The
mojo debug --vscodecommand now support the--init-commandand--stop-on-entryflags. Executemojo debug --helpfor more information. -
The Mojo LLDB debugger on VS Code now supports inspecting the raw attributes of variables that are handled as synthetic types, e.g.
Listfrom Mojo orstd::vectorfrom C++. -
The VS Code extension now allows selecting a default SDK when multiple are available.
❌ Removed
- The
UnsafePointer.bitcast()overload forDTypehas been removed. Wrap yourDTypein aScalar[my_dtype]to call the only overload ofbitcast()now.
🛠️ Fixed
-
Lifetime tracking is now fully field sensitive, which makes the uninitialized variable checker more precise.
-
Issue #1310 - Mojo permits the use of any constructor for implicit conversions
-
Issue #1632 - Mojo produces weird error when inout function is used in non mutating function
-
Issue #3444 - Raising init causing use of uninitialized variable
-
Issue #3544 - Known mutable
refargument are not optimized asnoaliasby LLVM. -
Issue #3559 - VariadicPack doesn't extend the lifetimes of the values it references.
-
Issue #3627 - Compiler overlooked exclusivity violation caused by
ref [MutableAnyOrigin] T -
Issue #3710 - Mojo frees memory while reference to it is still in use.
-
Issue #3805 - Crash When Initializing !llvm.ptr.
-
Issue #3816 - Ternary if-operator doesn't propagate origin information.
-
Issue #3815 - [BUG] Mutability not preserved when taking the union of two origins.
-
Issue #3829 - Poor error message when invoking a function pointer upon an argument of the wrong origin
-
Issue #3830 - Failures emitting register RValues to ref arguments.
-
The VS Code extension now auto-updates its private copy of the MAX SDK.
-
The variadic initializer for
SIMDnow works in parameter expressions. -
The VS Code extension now downloads its private copy of the MAX SDK in a way that prevents
ETXTBSYerrors on Linux. -
The VS Code extension now allows invoking a mojo formatter from SDK installations that contain white spaces in their path.
Special thanks
Special thanks to our community contributors: @soraos, @jjvraw, @bgreni, @thatstoasty, @szbergeron, @rd4com, @fknfilewalker, @gabrieldemarmiesse, @avitkauskas, and @martinvuyk.