Mojo nightly
This version is still a work in progress.
Language enhancements
-
Mojo supports an (internal only for now) feature known as interior origins, which allows collections to protect from a common class of memory unsafety problems.
List, for example, now returns element references bound to an interior origin of the list instead of the whole-list origin, so an element reference is invalidated when the list is mutated (for example byappend()orpop()). Code that holds an element reference across such a mutation is now correctly rejected by the lifetime checker instead of silently dangling after a reallocation:var list = [1, 2, 3]ref elem = list[0]list.append(4) # may reallocate, invalidating `elem`print(elem) # error: use of invalidated interior reference -
Mojo now support type inference from literals initializer.
var x : List[_] = [1, 2, 3]var x : List = [1.0, 2.0, 3.0] -
Mojo now support
==and!=for type equality check, and_type_is_eqis removed. -
Mojo now infers
TraitforTypeList.ofsuch thatcomptime TL = TypeList.of[Int, Bool]# works withoutcomptime TL = TypeList.of[Trait = AnyType, Int, Bool] -
Mojo now warns about redundant trait composition
# Warning: Redundant trait composition: 'Copyable' already implies 'AnyType'comptime T : AnyType & Copyable = xxx -
Keyword variadic arguments can now be forwarded to another function that takes keyword variadics, using Python style
**syntax:def takes_them(**kwargs: Int): ...def pass_them(**kwargs: Int):takes_them(**kwargs^) -
Dynamic function pointers with unbound type parameters can now be called directly. The compiler infers parameters from the call arguments and specializes the callee before the indirect call. This capability only works with a limited set of parameters - those which are specialized to a single value. This notably enables origin parameters on runtime function calls, which can also be implicit from variadics:
var fp1: def(*Int) thin -> Nonevar fp2: def[a: ImmOrigin](ref [a] x: Int) thin -> None...fp1(1, 2)fp2(42) -
Struct fields are no longer allowed to hide
UnsafeAnyOriginwithin a struct. For example, this is no longer accepted:struct Example:# error: cannot use UnsafeAnyOrigin in a struct field.var ptr: UnsafePointer[Int, MutUnsafeAnyOrigin]This is because Mojo doesn't know that uses of
Examplecontain anUnsafeAnyOriginand therefore doesn't do lifetime extension for values in its context. The typical solution for this is to add anOriginparameter but you can also useUntrackedOriginif you explicitly manage the lifetime of the underlying data:struct Example[origin: Origin]:var ptr: UnsafePointer[Int, Self.origin]# ORstruct Example:var ptr: UnsafePointer[Int, MutUntrackedOrigin]As a temporary workaround, you can decorate fields with
@__allow_legacy_any_origin_fieldsto ignore the compiler error, however this decorator is not stable and will eventually be removed. -
Import resolution behavior has been made consistent. When resolving an import of a module or package, in any given directory the resolution in order of preference is: source packages; precompiled
.mojocfiles; source modules; legacy precompiled.mojopkgfiles.Previously the behavior was unspecified and would pick whichever matching name it found in the directory first.
-
Added support for checking variadic type-list operands with
conforms_to(). For example, a variadic parameter list can pass its type-list value directly:def copy_variadic_elements[*Ts: AnyType](*args: *Ts) where conforms_to(Ts.values, Copyable):passTo check several distinct standalone types against a trait, conjoin scalar checks, for example
conforms_to(T, Trait) and conforms_to(U, Trait). -
Mojo has improved its tracking of import locations and is now able to show where a package containing a diagnostic was first introduced into the program:
Included from /bug.mojo:2:Included from /foo/__init__.mojo:3:Included from /foo/nested_pkg/__init__.mojo:4:/foo/nested_pkg/my_module.mojo:1:5: note: candidate not viable: unexpected argumentdef bar(): pass^Compared to the previous:
Included from /foo/nested_pkg/__init__.mojo:1:Included from /foo/nested_pkg/__init__.mojo:4:/foo/nested_pkg/my_module.mojo:1:5: note: candidate not viable: unexpected argumentdef bar(): pass^Note that for precompiled packages (
.mojocfiles), locations inside the package are omitted. For example, the above would instead resemble the following for a precompiledfoopackage:Included from /bug.mojo:2:/foo/nested_pkg/my_module.mojo:1:5: note: candidate not viable: unexpected argumentdef bar(): pass^Note also that for brevity the compiler does not report where any
stdpackages are pulled in as they're treated as privileged and implicitly imported into every module. This includes if the user explicitly imports all or part of the standard library themselves. -
immis now the preferred spelling for thereadargument and closure-capture convention.readstill works but will soon be deprecated.
Language changes
-
User-written structs must now explicitly declare closure-trait conformance in their inheritance list to satisfy a
def(...) -> ...closure trait. Previously a struct with a compatible__call__was accepted implicitly (duck-typing). Declare the trait in the struct's inheritance list:def apply[F: def(Int) -> Int](f: F, x: Int) -> Int:return f(x)struct Double(def(Int) -> Int): # previously: `struct Double:`def __call__(self, x: Int) capturing -> Int:return x * 2_ = apply(Double(), 5)Conformance is checked at struct definition rather than deferred to the use site.
-
Relative imports must now use
from(from . import foo); theimport .fooform is no longer accepted. -
Absolute imports
import a.b.cnow bind all ofa,a.b, anda.b.cinto the scope, where previously onlya.b.cwas made available. -
A bug in import handling has been fixed where absolute imports of a package followed by an import of one of its submodules no longer result in a compiler error.
import aimport a.b # fixed; was: "invalid redefinition of 'a'" -
A bug in function-scoped imports has been fixed, allowing dotted imports:
def foo():import a.ba.b.foo() # fixed; was: "use of unknown declaration 'a'"Note that this was already working correctly for other forms of import (
import a,from a import b,from a.b import c, etc). -
An imported package's submodules are now only accessible when the package's
__init__.mojore-exports those submodules.import pkg# only ok if pkg/__init__.mojo re-exports 'sub'.# Re-export submodules with, e.g.,# from . import sub# Use relative imports to avoid importing system packages.pkg.sub.foo()Note that absolute imports can always bring in that submodule, bypassing the
__init__.mojo:# always ok, regardless of the package's __init__.mojoimport pkg.submodulepkg.submodule.foo() -
Intra-package accesses without explicit
imports are now deprecated and will be removed in a future release:package/__init__.mojo:# Exported or re-exported symbolsdef foo(): passmodule1.mojo:# Module-defined symboldef bar(): passmodule2.mojo:# Previously able to implicitly use either of the above symbols, e.g.,foo()module1.bar()With this change,
module2.mojoabove must explicitly import symbols from elsewhere in the package:# module2.mojofrom . import foofrom . import module1foo()module1.bar() -
The
@explicit_destroydecorator is no longer sufficient for astructtype to opt-out ofImplicitlyDeletableconformance.As before, by default all Mojo structs implicitly conform to
ImplicitlyDeletable. Mojo now requires writing a constrainedImplicitlyDeletable where ...conformance to narrow or opt-out of that trait.This works both for types that are never
ImplicitlyDeletable(where False) and for types that are non-ImplicitlyDeletable based on a non-trivial condition (where <cond>):# no @explicit_destroy necessarystruct NeverDeletable(ImplicitlyDeletable where False):def destroy(deinit self):passcomptime assert not conforms_to(NeverDeletable, ImplicitlyDeletable)# no @explicit_destroy necessarystruct Container[T: AnyType](ImplicitlyDeletable where conforms_to(T, ImplicitlyDeletable)):var value: Self.Tcomptime assert conforms_to(Container[Int], ImplicitlyDeletable)comptime assert not conforms_to(Container[NonDeletable], ImplicitlyDeletable)Using
@explicit_destroywithout an argument error string is now an error, as it would have no effect or purpose.@explicit_destroy("custom error")can still be used to provide additional instruction to users when an instance cannot be deleted implicitly.This simplifies the language by replacing special decorator behavior with generalized struct conformance logic.
-
whereclauses inside a parameter list (for example,[x: Int where x > 0]) are no longer supported, following a period of deprecation. Use a trailingwhereclause after the signature instead:# Old (no longer supported):# fn foo[x: Int where x > 0]():# New:fn foo[x: Int]() where x > 0:pass -
The compiler now rejects newlines in the middle of certain statements, where they were previously permitted:
- Between
def/struct/trait/comptimekeywords and the following identifier - Between the
asyncanddefkeywords on function definitions - Anywhere in the midst of an
importstatement, save for parenthesized import lists.
- Between
-
It is now possible to import modules & packages through regular directories using the same path-like syntax.
For example, given the following structure:
dir└── nested_dir├── module.mojo└── package└── __init__.mojoIt is possible to import from the modules and packages inside the directories
dirandnested_dir:import dir.nested_dir.modulefrom dir.nested_dir.package import fooNote that an import statement resolving to a directory cannot later be used for scoped lookups as if it were a module or package:
import dirdir.nested_dir.package.foo() # error
Library stabilizations
-
trait ImplicitlyDeletable -
trait Movable -
trait Copyable -
trait ImplicitlyCopyable -
List
def __init__(out self)def __init__(out self, *, capacity: Int)def __init__(out self, *, copy: Self) where conforms_to(Self.T, Copyable):def __init__(out self, *, length: Int, fill: Self.T) where conforms_to(Self.T, Copyable):def __del__(deinit self) where conforms_to(Self.T, ImplicitlyDeletable):def reserve(mut self, capacity: Int):def resize(mut self, length: Int, fill: Self.T) where conforms_to(Self.T, Copyable & ImplicitlyDeletable):def __getitem__[origin: Origin, //](ref[origin] self, slice: ContiguousSlice) -> Span[Self.T, origin_of(self)._get_owned_interior["element"]]:def __init__(out self, *, length: Int, fill: Self.T) where conforms_to(Self.T, Copyable):def __iadd__(mut self, var other: Self, /) where conforms_to(Self.T, Copyable):def extend(mut self, var other: Self):def __contains__[dtype: DType, //](self: Span[Scalar[dtype], _]. value: Scalar[dtype]) -> Booldef __contains__(self, value: Self.T) -> Bool where conforms_to(Self.T, Equatable)
-
Bool
-
Span
def __init__(out self):def __init__(other: Span, out self: ImmSpan[other.T, other.origin]):
Library changes
-
When an unhandled error propagates out of
mainand no stack trace was collected, Mojo now prints a hint to setMODULAR_DEBUG=stack-trace-on-errorto enable stack trace collection, rather than printing only the error message. -
Various datatypes have adopted interior origins for increased memory safety, including
List,Deque,Variant,String,Dict,LinkedList,OwnedPointer, andHostBuffer. A reference or view into one of these containers now carries an interior origin, so one held across a mutation is rejected by the lifetime checker instead of silently dangling after a reallocation. For example, indexing aList(list[i]) returns a reference bound to the list:var list = [1, 2, 3]ref elem = list[0]list.append(4) # may reallocate, invalidating `elem`print(elem) # error: use of invalidated interior referenceHostBuffer.as_span()now returns aSpanbound to an interior origin of the buffer instead of the whole-buffer origin, so a span held across a mutation of the buffer is rejected by the lifetime checker:var buf = ctx.enqueue_create_host_buffer[DType.float32](4)var s = buf.as_span()buf[0] = 1.0 # mutates the buffer, invalidating `s`print(s[0]) # error: use of invalidated interior reference -
Added
Tuple.consume_elements, which moves each element out of a tuple into a caller-provided closure one at a time. Destructuring such asa, b = t^copies each element, so it cannot take apart a tuple whose elements areMovablebut notImplicitlyCopyable;consume_elementstransfers ownership instead, mirroringVariadicPack.consume_elements.var t = ([1, 2, 3], [4, 5, 6]) # `List` is not `ImplicitlyCopyable`@parameterdef handler[idx: Int](var elt: t.element_types[idx]):print(len(elt))t^.consume_elements[handler]() -
InlineArray's second parameter is renamed fromsizetolength.InlineArray.sizeremains as a deprecated alias forInlineArray.length; update any explicitInlineArray[T, size=N]toInlineArray[T, length=N], and.sizereads to.length. -
InlineArray's first parameter is renamed fromElementTypetoT. Any explicit usages must be updated. -
List.capacityis now acapacity()method instead of a public field. This keeps the allocated capacity out of the stable public field surface, since it should only change indirectly through operations likeappend(). Replacemy_list.capacitywithmy_list.capacity(). -
Renamed
StaticConstantOrigintoImmStaticOrigin, to align with theImm-prefixed spelling used for the other immutable origins. The old name is still available as a deprecated alias and will be removed in a future release. -
Floating-point
range()iteration is now drift-free and reversible. Elementiis computed asfma(i, step, start). Forward and reverse iteration produce identical sequences across repeated calls and across any IEEE-754 platform at the same floating-point width. Previously a step that was not exactly representable, such as0.1, could drift and yield an extra forward element thatreversed()then dropped. -
range()now rejects non-numeric element types (Booland the narrow MX float formats) at construction. The one- and two-argument float ranges (range(Float64(4.5))andrange(Float64(0.5), Float64(3.0))) are compile errors instead of infinite loops; use the three-argument stepped form. -
repr()of a scalarSIMDvalue (size == 1) now prints using its type alias instead of the verboseSIMD[DType.<dtype>, 1](...)form when the dtype has one. For example,repr(UInt32(4))is nowUInt32(4)(previouslySIMD[DType.uint32, 1](4)), andrepr(List[UInt](1, 2))is nowList[SIMD[DType.uint, 1]]([UInt(1), UInt(2)]).size > 1values, and scalar dtypes without an alias (such asDType.bool), keep theSIMD[...]form. This only affectsrepr();String(...)/print(...)output is unchanged. -
Added
Dict.clear_with(destroy_func), the closure counterpart ofclear(). Instead of destroying each entry in place, it hands the key and value todestroy_func, so it can clear aDictwhose key or value type is notImplicitlyDeletable. The dictionary's capacity is retained, so it stays reusable. -
Added
Dict.insert(key, value), which stores a key/value pair and returns the displaced entry as anOptional[DictEntry](empty when the key was not already present). Unlikedict[key] = value,insertdoes not destroy the displaced entry; it returns it, and the caller must destroy the returned entry. This is what letsinsertwork when the key or value type is notImplicitlyDeletable:var d = Dict[Int, Int]()var displaced = d.insert(1, 10) # None — key 1 was absentdisplaced = d.insert(1, 20) # the displaced (1, 10) entry -
Dict.fromkeys(keys, value)has been generalized from taking aListto accepting any iterable of keys. Both forms require the key and value types to beImplicitlyDeletable. -
By-reference
Dictiteration (for entry in dict,keys(),values(),items(), andreversed()) no longer requires the key and value types to beImplicitlyDeletable. These iterators only borrow references and never destroy an entry, so they now work on aDictwhose key or value type is notImplicitlyDeletable. Consuming iteration (for entry in dict^andtake_items()) still requiresImplicitlyDeletable, since it drops the entries it does not yield. -
Spanhas moved fromstd.memory.spantostd.collections.span. -
The container backing variadic
**kwargshas been renamed fromOwnedKwargsDicttoStringDict.StringDictno longer requires its value typeVto beImplicitlyDeletable. A keyword dictionary whose values are linear (non-ImplicitlyDeletable) is itself linear and must be torn down explicitly with the newdeinit_with(deinit_func), which hands each key and value todeinit_func. It also gainedinsert(key, value)(returns the displaced entry as anOptional[DictEntry]without destroying it) andpopitem()(moves out and returns a whole entry), mirroringDict. Operations that destroy a displaced value in place —kwargs[key] = valueand the two-argumentpop(key, default)— still requireVto beImplicitlyDeletable; useinsert,popitem, or the single-argumentpop(key)for linear values. -
Coordnow conforms toDevicePassable, so aCoordembedded in aDevicePassabletype (such as aTileTensor'sLayout) is encoded to the device throughCoord._to_device_typeinstead of a raw field bit-copy, the same wayIndexListalready was. -
reversed()now works on typed ranges such asreversed(range(Int16(1), 10, 2)). TheReversibleRangetrait gained an associatedReversedTypeiterator instead of hard-coding its__reversed__()return type, so every range flavor (including the typed scalar ranges) can conform and return its own reversed iterator. -
Added
copy_to_numpy_arrayandfrom_numpy_arrayto the newpython.numpymodule for moving flat numeric data between MojoSpan/Listand NumPy arrays without hand-writtenctypesplumbing:from std.python.numpy import from_numpy_array, copy_to_numpy_arrayvar values: List[Float64] = [1.0, 2.0, 3.0]var array = copy_to_numpy_array(values) # NumPy array (copies)var span = from_numpy_array[DType.float64](array) # borrow array as a SpanBoth support the fixed-width numeric dtypes.
copy_to_numpy_arraycopies its input into a new, independent array;from_numpy_arrayborrows the array's buffer zero-copy. -
Intis now an alias forScalar[DType.int]and integer literals materialize to thisScalartype. Because of this some conversions have become more strict.A new
SIMDLengthtype has been added for the width ofSIMDitself and must be used when inferring a parameter based on a SIMD argument like so:def frob[w: SIMDLength](v: SIMD[DType.int, w]): ...Alternatively the width can be unbound if you simply want to be parametric over any
SIMDtype:def frob(v: SIMD[DType.int, _])The new
Intshould still be used in all other situations.This type was briefly named
SIMDSizeearlier in this nightly cycle;SIMDSizeremains as a deprecated alias forSIMDLength. -
chdirhas been added to thestd.osmodule and anfchdirmethod has been added toio.FileDescriptor. These are wrappers for the corresponding POSIX functions. -
TypeList.all_conforms_to()is now implemented in terms ofconforms_to(), which supports parameter-list operands likeTs.values. As a result,all_conforms_to()constraints preserve the same proof structure as directconforms_to(Ts.values, Trait)constraints, so the compiler can use them in conditional conformance implication checks and type refinement.This means conditional conformances can rely on trait hierarchy relationships for an entire type parameter pack. Previously, a type that conditionally conformed to
JsonSerializablewould also need to repeat the inheritedSerializablecondition:trait Serializable:passtrait JsonSerializable(Serializable):passstruct Packet[*Ts: Movable](Serializable where Ts.all_conforms_to[Serializable](),JsonSerializable where Ts.all_conforms_to[JsonSerializable](),Movable,):passNow the
JsonSerializablecondition is enough for the compiler to prove the inheritedSerializableconformance:struct Packet[*Ts: Movable](- Serializable where Ts.all_conforms_to[Serializable](),JsonSerializable where Ts.all_conforms_to[JsonSerializable](),Movable,):passThe same constraints now refine each element of a variadic type parameter pack inside
where,comptime assert, andcomptime ifcontexts:def write_all[*Ts: Movable](mut writer: Some[Writer], *args: *Ts):comptime if Ts.all_conforms_to[Writable]():comptime for i in range(args.__len__()):args[i].write_to(writer) -
ImplicitlyDestructiblehas been renamed toImplicitlyDeletable, for better name consistency with its required__del__()"delete" special method. -
is_trivially_destructible()has been renamed tois_trivially_deletable(), for consistency with theImplicitlyDeletablerename. It now also accepts any type (T: AnyType) instead of requiringT: ImplicitlyDeletable, returningFalsefor non-ImplicitlyDeletable(linear) types. -
List.resizeandList.shrinknew_sizearguments have been renamed tonew_length. -
The
valueargument ofList.resizehas been renamed tofillto match List's constructor. -
The
Reflected.field_type[name]reflection member has been renamed toReflected.field[name], because it returns a chainableReflectedhandle for the named field rather than the field's bare type, so the old name was not accurate. Retrieve the field's type from the handle's.Tmember, as inreflect[T].field["x"].T. Update call sites such asreflect[T].field_type["x"]toreflect[T].field["x"]. -
Several collection types now conditionally conform to
ImplicitlyDeletable, conforming only when their element type does. This lets a collection hold non-ImplicitlyDeletableelements at all (previously such a collection failed to compile); a collection of non-deletable elements is itself linear and must be drained explicitly with the newdeinit_with()method, which calls a closure on each element:collection^.deinit_with(my_destroy_closure)Generic code that takes one of these collections by value may now need
& ImplicitlyDeletableadded to its element bound so the collection can be dropped:def foo[T: Movable & ImplicitlyDeletable, //](var arr: InlineArray[T, 3]):passAffected types:
InlineArray[ElementType, size].Deque[ElementType]- Element-destroying operations (
append,appendleft,extend,extendleft,insert,clear,remove, etc.) still requireElementTypeto beImplicitlyDeletable. - Consuming iteration (
for x in deque^, theIterableOwnedconformance) is likewise conditional, requiringElementTypeto beImplicitlyDeletable; generic code bounded onIterableOwnednow rejects a non-conforming element type at the bound rather than failing later inside__iter__(). For deletable element types (the common case) this is transparent.
- Element-destroying operations (
Dict[KeyType, ValueType, HasherType]- Element-destroying and key/value-copying operations (
__setitem__,setdefault,fromkeys,update,__or__,__ior__,pop,clear) still require theKkey andVvalue types to beImplicitlyDeletable, so aDictwith non-ImplicitlyDeletablekeys or values can currently be constructed and torn down withdeinit_with()but not populated or mutated. For deletable key/value types (the common case) this is transparent. - Consuming iteration (
for entry in dict^) is likewise conditional, requiringValueTypeto beImplicitlyDeletable.
- Element-destroying and key/value-copying operations (
LinkedList[ElementType]- Unlike
Dict, aLinkedListwith non-ImplicitlyDeletableelements can be populated (append,prepend,insert,extend) and then torn down withdeinit_with(). - Only
clearstill requiresElementTypeto beImplicitlyDeletable. For deletable element types (the common case) this is transparent. LinkedList.insert()no longer raises on an out-of-range index; likeList.insert(), it now aborts (checked when asserts are enabled).- Consuming iteration (
for x in list^, theIterableOwnedconformance) is likewise conditional, requiringElementTypeto beImplicitlyDeletable.
- Unlike
Tuple[*element_types]- A tuple is now
ImplicitlyDeletableonly when every element type is. A tuple with a non-ImplicitlyDeletableelement is linear and must be torn down with the newdeinit_with()method (or fully consumed withconsume_elements()). For deletable element types (the common case) this is transparent. Generic code that stores aTuple[*Ts]with an unbounded pack may need& ImplicitlyDeletableon the pack bound to keep dropping the tuple implicitly.
- A tuple is now
Set[ElementType, HasherType]- The element bound loosened from
KeyElement & ImplicitlyDeletableto justKeyElement, so aSetcan now hold a non-ImplicitlyDeletableelement type. - Like
Dict, element-mutating operations (add,remove,discard,clear) still requireElementTypeto beImplicitlyDeletable, so such aSetcan currently be constructed and torn down withdeinit_with()but not populated. For deletable element types (the common case) this is transparent. - Consuming iteration (
for x in set^) is likewise conditional, requiringElementTypeto beImplicitlyDeletable.
- The element bound loosened from
-
InlineArray's element type bound loosened fromMovabletoAnyType, so anInlineArraycan now hold a non-Movableelement type. TheMovableconformance is now conditional on the element: move construction (including list-literal construction such as[a, b, c]) requires aMovableelement, while indexing, by-reference iteration, and destruction do not. Code that usesMovableelement types is unaffected, since aMovableelement still yields a movable array. -
Is is now possible to iterate over owned elements in
List,Dict,InlineArray,LinkedList, andSetwhen the element type is notCopyable:def iterate[T: Movable](var list: List[T]):# Consume elementsfor var x in list^:passThe
IterableOwnedconformance on several collections is now conditional on the element type conforming toMovable & ImplicitlyDeletable, droppingCopyable.Additionally, generic code bounded on
IterableOwnednow rejects a collection of non-conforming elements at the bound, rather than failing later inside__iter__(). -
The implicit conversion constructors that cast an
UnsafePointertoMutUnsafeAnyOriginorImmUnsafeAnyOriginare now deprecated and emit a deprecation warning when used.UnsafeAnyOriginis an unsafe escape hatch that silently extends unrelated lifetimes and disables exclusivity checking, so it should never be applied implicitly. Prefer keeping a concrete origin; if you must discard it, make the cast explicit with theas_unsafe_any_origin()method. -
Added
reflect[T].field_at[idx]to the reflection API, the by-index dual ofreflect[T].field[name]. It returns the reflection handle for the type of the field atidx, so a field's concrete type can be recovered while iterating fields by index (where the name is not available as a literal):comptime y_type = reflect[Point].field_at[1]var v: y_type.T = 3.14 # y_type.T is the concrete field type -
Removed the implicit constructors that converted an
UnsafePointerinto anOptional[UnsafePointer[..., UnsafeAnyOrigin]]. Constructing anOptional[UnsafePointer]now preserves the pointer's real origin instead of silently widening it toUnsafeAnyOrigin. Two call-site updates may be needed:-
Passing a concrete pointer where the parameter's origin is a genuinely fixed
MutAnyOrigin/ImmutAnyOrigin(typically C-FFI signatures) now requires an explicitas_unsafe_any_origin(). -
Because origins are now preserved, exclusivity checking applies to
memcpy()(and similar) calls whosedestandsrcderive from the same buffer. An intra-buffer copy that previously compiled now errors with "argument of 'memcpy' call allows writing a memory location previously writable through another aliased argument". Opt out by making one argument an unsafe any-origin (the non-overlap ofdestandsrcis already amemcpy()precondition):memcpy(dest=buf + dst_off,src=(buf + src_off).as_unsafe_any_origin(),count=n,)
-
-
coordis now a comptime expression, andcoord[DType]()has been renamed todyn_coord[DType](). Now one can just write:var my_coord = coord[1, 2, 3]to create a
Coord[ComptimeInt[1], ComptimeInt[2], ComptimeInt[3]] -
Removed
trait_downcast_var(). Improvements to type refinement based onwhere conforms_to(..)andcomptime assert conforms_to(..)make explicit value trait downcasting no longer necessary. -
The
ConditionalTypetype function instd.utils.type_functionsis now deprecated. Use the equivalent ternary expressionT if cond else Uinstead:# Deprecated:comptime Storage = ConditionalType[If=cond, Then=Int, Else=NoneType]# Use instead:comptime Storage = Int if cond else NoneType -
Added
raise_python_exception()tostd.python.bindings, which translates a MojoErrorinto a Python exception viaPyErr_SetStringand returns a nullPyObjectPtr. -
The
PyCFunctionFastcalling convention used byPythonModuleBuilder.def_py_c_function()forMETH_FASTCALLcallbacks now declares its argument array as a safePointer[PyObjectPtr, MutUntrackedOrigin]instead of anUnsafePointer. The two types share the same layout, so the C ABI is unchanged; hand-written fastcall callbacks only need to update the parameter's spelling in their signature and read the borrowed arguments withargs[unsafe_offset=i]. -
Typed-self methods registered through
PythonTypeBuilder.def_method()now declare their self parameter as a safePointer[Self]instead of anUnsafePointer[Self], and the extension argument helperscheck_and_get_arg()andcheck_and_get_or_convert_arg()return a safePointer. The two pointer types share the same layout, so behavior is unchanged; update method signatures to spellPointer(for example,self_ptr: Pointer[mut=True, Self]). -
Iterating over a
String,StringSlice, orStringLiteralnow yields grapheme clusters by default. Their__iter__()and__reversed__()methods return aGraphemeSliceIter, sofor c in my_string:produces what a user perceives as a single "character" on screen. The lower-level views remain available when you want them:codepoints()orcodepoint_slices()for Unicode scalars, andbytes()for raw UTF-8 bytes. -
The
Equatabletrait now allows for positional-only implementations, and argument on implementers no longer need to match the trait exactly. -
PointerandUnsafePointerhave had theirtypeparameter renamed toT. -
UnsafePointer.init_pointee_move()andUnsafePointer.init_pointee_copy()are now deprecated in favor of a singleunsafe_write()method. Moving a value in works the same as before:ptr.unsafe_write(value^)To copy a value in instead of moving it, pass it as the
copykeyword argument:ptr.unsafe_write(copy=value) -
UnsafePointer.destroy_pointee()andUnsafePointer.destroy_pointee_with()are now deprecated in favor of the newunsafe_deinit_pointee()method, which covers both cases: call it with no arguments to destroy anImplicitlyDeletablepointee, or pass a deinitializing closure to destroy a non-ImplicitlyDeletablepointee in place. -
Pointergained explicitunsafe_-prefixed methods for operations that are individually unsafe — unchecked bounds, aliasing casts, moving or overwriting memory — rather than requiring the whole pointer to be typed unsafe:unsafe_offset(),unsafe_load(),unsafe_store(),unsafe_strided_load(),unsafe_strided_store(),unsafe_gather(),unsafe_scatter(),unsafe_as_noalias(),unsafe_address_space_cast(), andunsafe_take_pointee(). These methods work on anyPointer. The previous unprefixed names still work, but are now hidden from the generated docs and remain gated behind an unsafe pointer type; prefer theunsafe_-prefixed names going forward. Each method's docstring documents the exactSafety:requirements the caller must uphold. -
UnsafePointer.init_pointee_move_from()is now deprecated in favor of the newunsafe_write_move_from()method, which moves the value out of a source pointer into the uninitialized memoryselfpoints to (leaving the source uninitialized):dst.unsafe_write_move_from(src)Like
unsafe_write()andunsafe_take_pointee(), this method works on anyPointer— the oldinit_pointee_move_from()was gated behind an unsafe pointer type, so callers no longer need to wrap safe pointers inMutUnsafePointerto move a value between them. -
OwnedDLHandle.get_functionnow returns a callable that keeps the owning handle alive while it runs, fixing a crash where the library could bedlclosed between symbol lookup and the call. Its parameter is now the return type instead of the full function-pointer type, and it raises if the symbol is missing (previously it aborted the process):# Before:var sqrt = lib.get_function[def(Float64) abi("C") -> Float64]("sqrt")# After:var sqrt = lib.get_function[Float64]("sqrt")Arguments are passed using the Mojo calling convention, which is correct for scalar and register-passable arguments. Multi-field struct arguments are rejected at compile time because the Mojo and C conventions can disagree on how aggregates are passed.
Tooling changes
-
Added a
--lld-pathCLI flag. This overrides the LLD path that Mojo uses. -
mojo-lsp-serverno longer parses or type-checks code blocks inside docstrings by default. This checking rests on unstable foundations in the LSP server and was prone to failing, producing false-positive diagnostics unrelated to the code being edited, for little value in return. Pass-check-docstringswhen launchingmojo-lsp-serverfrom the command line to re-enable the previous behavior. We plan to make this checking more robust and re-enable it by default over time.
GPU programming
-
Added programmatic Metal GPU frame capture in
std.gpu.host:_start_metal_trace_capture(ctx, path)and_end_metal_trace_capture(ctx)bracket GPU work and write a.gputracefile for offline replay (requiresMTL_CAPTURE_ENABLED=1). A_set_metal_gpu_print_enabled(ctx, enabled)toggle and theMODULAR_DISABLE_METAL_GPU_PRINTenvironment variable disable Metalos_logGPU print; print is also suppressed during a capture, which otherwise cannot be replayed. -
A bare
--target-acceleratorarchitecture (for examplegfx950orsm_90) is now handled identically to its vendor-prefixed form (amdgpu:gfx950,nvidia:sm_90). Previouslyhas_amd_gpu_accelerator(),has_nvidia_gpu_accelerator(), andhas_apple_gpu_accelerator()only recognized the vendor-prefixed spelling, so code that specialized on them (such as warp-tiling parameters) could silently take the wrong path and fail a downstreamcomptimeconstraint.amd:<arch>is also now accepted as an alias foramdgpu:<arch>, mirroring the existingnvidia:<arch>prefix. -
The GPU
Vendortype can now be imported fromstd.sys(from std.sys import Vendor). It remains importable fromstd.gpu.host.infofor backward compatibility. -
DeviceContext.load_functionnow keys its runtime cache on the requested entry-point name as well as the blob. Loading two different entry points (for examplekernel_aandkernel_b) from a single PTX/cubin blob no longer collides — previously the second load silently returned the function resolved by the first. The cache also no longer keys on the entire blob when no module name is supplied: it keys on a short hash of the blob instead, so each call avoids copying, hashing, and byte-comparing the whole blob (and retaining a duplicate of it). The win scales with blob size and matters most for large multi-entry blobs loaded on the per-execution path. -
The
DeviceStreamtype is now included in the API reference documentation. Returned byDeviceContext.create_stream()andDeviceContext.create_external_stream(), it provides methods for synchronizing and sequencing asynchronous GPU work (for example,synchronize(),record_event(), andenqueue_wait_for()). The type was already public but was previously hidden from the generated docs. -
Added an 8x8
simdgroup_matrixmatrix multiply-accumulate primitive (_mma_apple_8x8()) withapple_mma_load_8x8()/apple_mma_store_8x8()fragment helpers for Apple Silicon GPUs instd.gpu.compute.arch. Unlike the 16x16 path (Apple M5 only), the 8x8 primitive is available on all Apple GPU generations (M1-M5). It acceptsFloat16,BFloat16, andFloat32inputs with aFloat32accumulator. -
Atomic.compare_exchange()now accepts aweakparameter, and requiresweak=Trueto compile on Apple GPU targets: AIR exposes no strong compare-exchange primitive, so Metal only lowers theweakform. This is safe for the common case of a CAS-retry loop, since a spurious failure just costs one extra iteration. Previously any use ofcompare_exchange(), including helpers built on it like atomic scatter-reduce, failed to compile on Metal. -
Apple M5
simdgroup_matrixMMA now accepts FP8 (float8_e4m3fn,float8_e5m2) inputs with an F32 accumulator, alongside the existing F16/BF16/F32 and 8-bit integer types. -
Added
warp.match_any(), which returns, for each warp lane, the mask of lanes whose value has the same bits. It uses NVIDIA'smatch.any.syncinstruction, areadfirstlaneballot fold on AMD, and a shuffle-based emulation on Apple Silicon GPUs. -
Added
warp.match_all(), which returns the warp's active-lane mask if every lane holds the same bits and 0 otherwise. It uses NVIDIA'smatch.all.syncinstruction, areadfirstlaneballot fold on AMD, and a shuffle-based check on Apple Silicon GPUs. -
warp.vote()now works on Apple Silicon GPUs. Metal's AIR backend exposes no usable ballot intrinsic, so it emulates the ballot with an XOR-butterfly OR-reduction oversimd_shuffle_xor, returning a 32-bit mask (or aDType.uint64mask whose upper 32 bits are always zero); NVIDIA and AMD are unchanged. -
DeviceGraphBuilder.collect_dependenciesnow accepts an optionaldependenciesargument. The named predecessor handles are injected as ambient predecessors of every node theworkclosure adds, so the scope's nodes run after those predecessors without the closure threading the handles through to eachadd_*call. With the default (empty)dependenciesthe behavior is unchanged. Whenworkadds no nodes, the returned join node falls back to depending ondependenciesso it still chains correctly.var producers = builder.collect_dependencies(add_producers)# Every node added by `add_consumers` depends on `producers`:var consumers = builder.collect_dependencies(add_consumers, dependencies=[producers]) -
Added a
DeviceGraphBuilder.add_functionoverload that takes the kernel as a compile-time parameter and compiles it automatically, mirroring the parameter-basedDeviceContext.enqueue_function. Callers no longer need a separateDeviceContext.compile_functionstep to add a kernel node:def build(mut builder: DeviceGraphBuilder) raises {read}:_ = builder.add_function[kernel](42, grid_dim=1, block_dim=1, dependencies=[]) -
DeviceGraphBuilder.add_functionnow covers every liveDeviceContext.enqueue_functionform, so any kernel launchable on a device context can also be recorded as a graph node:- Added an overload accepting a
DeviceExternalFunctionloaded from PTX/SASS viaDeviceContext.load_function(). - Added an overload taking a capturing kernel as a compile-time parameter
with runtime arguments, mirroring the capturing parameter-based
DeviceContext.enqueue_function. - All
add_functionoverloads now accept alocationargument so wrappers can attribute launch errors to their callers, and the closure overload now accepts (and honors) afunc_attributeargument.
- Added an overload accepting a
-
AddressSpaceis now target-extensible rather than a fixed, portable enum. The built-in GPU spaces (GENERIC,GLOBAL,SHARED,CONSTANT,LOCAL,SHARED_CLUSTER,BUFFER_RESOURCE) are unchanged, but accessing any other name — for example an accelerator-specificAddressSpace.SCRATCHPAD— now resolves through the active hardware backend instead of being a hard-coded compile error. The set of valid address-space names is the union of the built-in GPU spaces and whatever the active backend defines, so accelerator backends can provide their own named spaces (with their own values) only where they exist. A name that no backend defines remains a compile-time error. -
Added support for the Steam Deck's RDNA2 Van Gogh APU.
-
The
layoutpackage is now bundled with MAX instead of Mojo.
Removed
-
Removed the
UInt-returning GPU indexing accessors (thread_idx_uint,block_idx_uint,block_dim_uint,grid_dim_uint,global_idx_uint,lane_id_uint,warp_id_uint). Use theInt-returningthread_idx,block_idx,block_dim,grid_dim,global_idx,lane_id, andwarp_idaccessors instead. -
Removed the
store_volatile()andload_volatile()intrinsics fromstd.gpu.intrinsics. UseUnsafePointer.store[volatile=True]()andUnsafePointer.load[volatile=True]()instead, which work across all supported GPU targets rather than NVIDIA only. -
Removed the deprecated
GPUAddressSpacealias forAddressSpace. UseAddressSpacedirectly. -
Removed the
DType.invalidsentinel alias. Code that used it to represent an absent or optional dtype should useOptional[DType]instead. Accordingly,DType._from_str()now returns anOptional[DType](Nonewhen the string does not name a dtype) rather thanDType.invalid. -
Removed positional indexing on
StringLiteral(literal[i]). It allowed out-of-bounds reads and was inconsistent with the[byte=],[codepoint=], and[grapheme=]indexing scheme used byStringandStringSlice. Use those keyword accessors instead (for example, on aStaticString).
Fixed
-
base64.b16decodenow raises on invalid input instead of silently producing corrupt output. -
#6784, #6434 -
math.sqrtonFloat64now works on NVIDIA GPU. It lowers to the IEEE correctly-rounded hardware sqrt (sqrt.rn.f64) instead of being rejected at compile time. NVIDIA has no approximate f64 sqrt, so theFloat32fast path continues to usesqrt.approx.ftz.f32. -
#4473 - The
offsetparameter ofFileHandle.seek()(andNamedTemporaryFile.seek()) is now a signedIntinstead ofUInt64, so negative offsets relative toos.SEEK_CURoros.SEEK_ENDwork as the docstrings already showed. Previously a negative offset only compiled as a literal (via unsigned wrap-around) and could not be passed from a signed variable. -
#6755 - Volatile loads are no longer removed when their results are unused.
-
Type refinement from a
conforms_to()guard now applies inside the branches of a ternaryexp1 if cond else exp2used in acomptimecontext, matching the existingcomptime ifstatement behavior. For example, this now compiles:trait HasProperty:comptime property: Intcomptime get_property_or[T: AnyType] =T.property if conforms_to(T, HasProperty) else 0Previously the true branch failed with
'AnyType' value has no attribute 'property'becauseTwas not refined under the guard. -
A
comptimemember with a trailingwhereclause is now accepted as a witness for a conditional trait conformance when the conformance constraint implies the member's constraint, for example:trait StaticSize:comptime SIZE: Intstruct Foo[size: Int = -1](StaticSize where size >= 0):comptime SIZE: Int where Self.size >= 0 = Self.size -
The reflection-based default
Equatableimplementation no longer fails to compile for single-elementRegisterPassablestructs. Such a struct is flattened to its sole field's type, which previously caused the reflectionfield_refto produce an invalidkgen.struct.gep. For example, this now compiles and printsTrue:@fieldwise_initstruct Inner(Equatable, RegisterPassable):var x: Intvar y: Int@fieldwise_initstruct Outer(Equatable, RegisterPassable):var inner: Innerdef main():var o = Outer(Inner(1, 2))print(o == o) -
A method whose return type references a constrained
comptimemember (one declared with a trailingwhereclause) is now accepted when the method's ownwhereclause discharges that member's constraint.trait Operation:comptime Output: AnyTypedef operate(self) -> Self.Output: ...struct MyList[T: AnyType](Operation where conforms_to(T, Movable)):comptime Output: AnyType where conforms_to(Self.T, Movable) = Intdef operate(self) -> Self.Output where conforms_to(Self.T, Movable):return Int(123) -
A method whose return type is a generic struct instantiated with a parameter that only satisfies the struct's declared trait bound via the method's own
whereclause (rather than via the parameter's own declaration) is now accepted, instead of spuriously rejecting the returned value as a different, unconvertible type.struct Collection[T: AnyType](Movable):def foo(var self,) -> Iter[Self.T] where conforms_to(Self.T, ImplicitlyDeletable):return Iter(self^)@fieldwise_initstruct Iter[T: ImplicitlyDeletable]:var _collection: Collection[Self.T] -
A struct using
where Falseto opt out of a builtin trait's implicit synthesis (e.g.Movable where False) no longer spuriously fails to compile when one of its fields also opts out of that same trait. For example, this now compiles:struct One(Movable where False):passstruct Two(Movable where False):var y: One -
A failed import no longer poisons its name for the rest of the compilation. Previously, after something like
import pkg.utilfailed to resolve, a laterimport utilwould silently bind the cached failure even when a realutil.mojoexists on the search path, making the module unimportable with no diagnostic. -
#6485 -
Optional[T]andVariant[...]no longer corrupt data for payload types that include aBoolfield. The fix changes how unions are lowered to LLVM.