Mojo v1.0.0
Highlights
With Mojo 1.0 we're staring to define the stability policies for the Mojo language and standard library. We've started marking standard library APIs as stable, meaning they won't be removed or changed in a way that breaks source compatibility, beginning with a deliberately small set that we'll grow in subsequent releases. For details, see Library stabilizations.
We worked hard on getting names, defaults, and safety boundaries right for 1.0. That does mean you'll find more breaking changes than usual in this release. But nearly every breaking change ships with a deprecated alias and a compiler fix-it, so migration is mechanical.
-
Language ergonomics: Mojo gained
lambdaexpressions—anonymous, single-expression closures that desugar to a nesteddef. List expressions now construct anArrayrather than aListby default, and keyword variadics can be forwarded to another function with Python-style**syntax. See Language enhancements. -
Memory safety and pointer unification:
PointerandUnsafePointerare unified into a singlePointertype, with unsafety now marked on the individual operation instead of on the type as a whole.UnsafeAnyOriginis correspondingly harder to acquire by accident: implicit widening to it is deprecated, and a struct field can no longer hide one. See Pointers and memory. -
The lifetime checker now understands container interiors: a new experimental feature known as interior origins lets
List,Dict,String, and several other types return element references bound to an interior origin, so code that holds an element reference across a mutation is rejected instead of silently dangling after a reallocation. See Language enhancements and Collections and iterators. -
Explicit over implicit: a number of inference rules and silent defaults now have to be written down. Declaring a variable without
varis deprecated, a method'sselfmust have typeSelf, and a bare**kwargsmust be spelledvar **kwargs. The import system has been overhauled to make name resolution explicit and consistent. See Language changes. -
One name, and one type, per concept: this release consolidates vocabulary that had drifted.
sizebecomeslengththroughout,InlineArraybecomesArray,StringSlicebecomesStringSpan,ImplicitlyDestructiblebecomesDeinitablewith the destructor spelled__deinit__(), andreadbecomesimm. Duplicated types are consolidated too:Intis now an alias forScalar[DType.int], and theInt-based andScalar-basedrange()types are unified into a singledtype-parameterized family. See Library changes and SIMD and numeric types. -
Collections accept more types: a type that models a unique resource can now live in a container. Several collection types conform to
Deinitableonly when their element type does, so a collection of explicitly-destroyed elements now compiles where it previously did not; such a collection must be drained with the newdeinit_with()method.OptionalandVariantalso accept element types that are notMovable. See Explicitly-destroyed types and deinitialization. -
Constraints do more of the work: a
whereclause accepts a string-literal message the compiler reports when the constraint fails,TypeList.all_conforms_to()refines each element of a parameter pack, and type equality is now spelled with==and!=. Struct types areMovableby default, withMovable where <cond>to narrow the conformance. See Language enhancements and Type system and traits. -
Correct by default, even at the cost of convenience: APIs that quietly did something defensible but wrong now refuse or do the right thing instead. Indexing with an invalid contiguous slice exits the program instead of silently clamping or wrapping it. Iterating over a string yields grapheme clusters by default, so
for c in my_stringproduces what a user perceives as a single character on screen.range()rejects non-numeric element types and the float forms that used to loop forever, andsize_of()returns the allocation size, fixing memory corruption for over-aligned types inList. See Collections and iterators, String and text, and SIMD and numeric types. -
Faster Python interoperability:
PythonObjectarithmetic, comparison, and membership operators now dispatch through CPython's abstract protocols instead of a Python-level attribute lookup followed by a bound-method call. This is roughly 12x faster on the interop hot path and follows standard Python operator semantics more closely. See Python interoperability. -
A clearer boundary between Mojo and MAX: some standard library APIs related to accelerator programming have moved to a new
maxMojo package, and thelayoutpackage is now bundled with MAX instead of Mojo. Relatedly,IntandUIntno longer conform toDevicePassableand can no longer be passed to GPU kernels: they are platform-sized index types, so passing them to an accelerator miscompiles when the host and device disagree on the width. Use a fixed-width type such asInt32instead. See Package moves and the MAX 26.5 changelog.
Documentation
-
Lambda expressions are covered in Lambda expressions in the Mojo Manual and in the Language reference.
-
Added one-page Mojo cheat sheets for quick reference.
-
Refreshed the pointer documentation for the new, unified
Pointertype and layout-based allocation. -
Rewrote the traits page from scratch.
-
Moved GPU programming and related topics to the MAX documentation.
Language enhancements
-
Support for
lambdaexpressions: anonymous, single-expression closures that desugar to a nesteddef. As in Python, the body is a single expression with noreturn; unlike Python, the arguments are parenthesized and typed as in adefsignature—for example,lambda (x: Int) {} -> Int: x + 1. You can elide the capture list{…}and the return type: an omitted capture list imm-captures the body's free variables (and is thin when there are none), and an omitted return type defaults toNone—so the barelambda: expris valid whenexprisNone-typed. These are fixed defaults, not inference (a non-Nonebody still needs an explicit-> T).A thin (capture-free)
lambdais a function value, exactly like adefreferenced by name; any otherlambdais a closure instance, a runtime value with no function type. For the full rules, see the lambda expressions reference page. -
Mojo supports a new experimental feature known as interior origins, which allows collections to protect against a common class of memory-safety problems.
List, for example, now returns element references bound to an interior origin of the list instead of the whole-list origin, so mutating the list (withappend()orpop(), for example) invalidates an element reference. The lifetime checker now correctly rejects code that holds an element reference across such a mutation, instead of letting it silently dangle after a reallocation:var list = [1, 2, 3]ref elem = list[0]list.append(4) # may reallocate, so `elem` in invalidatedprint(elem) # error: use of invalidated interior reference -
Mojo now picks
Array(instead ofList) as the default type to construct from a list expression, eliminating implicit heap allocations. For example:var x = [1, 2, 3]# type_of(x) = Array[Int, 3]Mojo also now supports type inference from literal initializers:
var x: List[_] = [1, 2, 3]var y: List = [1.0, 2.0, 3.0] -
Struct types are now
Movableby default. To opt out of always-on movability, either explicitly specify a conditionallyMovableconformance usingMovable where <cond>, or opt out ofMovableconformance entirely usingMovable where False. -
You can now forward keyword variadic arguments to another function that takes keyword variadics, using Python-style
**syntax:def takes_them(var **kwargs: Int): ...def pass_them(var **kwargs: Int):takes_them(**kwargs^) -
You can now call dynamic function pointers with unbound type parameters directly; the compiler infers the parameters from the call arguments and specializes the callee before the indirect call. This works only for parameters specialized to a single value, and notably enables origin parameters on runtime function calls:
var fp: def[a: ImmOrigin](ref [a] x: Int) thin -> Nonefp(42) -
Mojo now supports
==and!=for type equality checks. -
whereclauses now accept an optional string-literal message, writtenwhere (condition, "message"). The compiler includes the message in the diagnostic when the constraint fails, and supports it everywherewhereclauses are allowed: trailing function and struct constraints, struct conditional-conformance clauses, andalias/comptimedeclarations.def foo[sc: Int]() where (sc > 1, "scaling factor must be greater than 1"):... -
TypeList.all_conforms_to()constraints now preserve the same proof structure as directconforms_to()constraints, so the compiler can use them as evidence in conditional conformance checks and type refinement. Conditional conformances can therefore rely on trait hierarchy relationships for an entire type parameter pack: given a traitJsonSerializablethat inherits fromSerializable, a conditionally conforming type no longer has to repeat the inherited condition. The same constraints also refine each element of a variadic type parameter pack insidewhere,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__()):# previously required rebinds to refine types, now just worksargs[i].write_to(writer) -
Mojo now infers
TraitforTypeList.of, such that:comptime 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 -
Mojo has improved its tracking of import locations and now shows where a package containing a diagnostic was first introduced into the program:
Included from /bug.mojo:2:Included from /foo/__init__.mojo:3:/foo/nested_pkg/my_module.mojo:1:5: note: candidate not viable: unexpected argumentFor precompiled packages (
.mojocfiles), the compiler omits locations inside the package. It also doesn't report wherestdpackages are pulled in, since they are implicitly imported into every module.
Language changes
-
All variable declarations should use
var. Implicit variable declarations are deprecated, and now warn with a fix-it that insertsvar:x = 0 # implicit declaration of 'x' is deprecated; add 'var' before the namevar x = 0 # Fixed; no warningEvery first assignment to a name warns,
:=walrus targets and a barex: Tannotation included. Binding forms that already spell out how they bind are unaffected:fortargets,with ... as,except ... as, comprehension targets, and the_discard. -
The destructor dunder method should now be spelled
__deinit__(), for naming parity with__init__(). The old__del__()spelling still works but now emits a deprecation warning with a fix-it to rename it. -
The import system has been overhauled to make name resolution explicit and consistent:
-
Import resolution now follows a consistent preference order within a directory: source packages, then precompiled
.mojocfiles, then source modules, then legacy precompiled.mojopkgfiles. Previously the order was unspecified. -
Relative imports must use
from(from . import foo); theimport .fooform no longer works. -
Absolute imports
import a.b.cnow bind all ofa,a.b, anda.b.cinto the scope, where previously onlya.b.cwas available. Two related bugs are fixed:import afollowed byimport a.bno longer errors with "invalid redefinition of 'a'", and function-scoped dotted imports (import a.binside a function body) now work. -
An imported package's submodules are now only accessible when the package's
__init__.mojore-exports them (for example, withfrom . import sub). An absolute import of the submodule (import pkg.submodule) always works, bypassing the__init__.mojo. -
Intra-package accesses without explicit
imports are deprecated and will be removed in a future release. A module must now explicitly import symbols defined elsewhere in its own package, withfrom . import foo. -
You can now import modules and packages through regular (non-package) directories using the same path-like syntax, for example,
import dir.nested_dir.module. An import statement that resolves to a directory cannot itself be used for scoped lookups (import dirthendir.nested_dir.module.foo()is an error). -
A standalone module can no longer import its own name (for example,
import utilinsideutil.mojo), which could only shadow a same-named package on the search path. Modules inside packages are unaffected. -
Importing functions with the same name from different modules, combining them into one overload set, is now deprecated and emits a warning; a future release will reject the second import. Import the name from a single module instead.
-
Wildcard imports now resolve latest first, textually: declarations imported last shadow earlier ones, including those implicitly imported from
std.prelude. -
The compiler now emits error diagnostics on failed imports per import site, instead of once per module.
-
-
immis now the preferred spelling for thereadargument and closure-capture convention.readstill works but will soon be deprecated. -
size_of()now returns the allocation size: the store size rounded up to the type's alignment, which is the stride between adjacent elements of an array of that type. This changes the result only for types whose store size is not a multiple of their alignment (for example, structs whose@align(N)exceeds their natural alignment) and fixes memory corruption when such types were used inListand other collections whose growth copiescount * size_ofbytes. -
The
@explicit_destroydecorator no longer opts a struct out ofDeinitableconformance. UseDeinitable where Falseinstead:struct ExplicitDestroy(Deinitable where False):def destroy(deinit self):passYou can also make a type conditionally explicitly-destroyed by using a non-trivial condition, like
Deinitable where conforms_to(T, Deinitable).Use
@explicit_destroy("custom error")to give users additional instruction when an instance cannot be deleted implicitly. Using@explicit_destroywithout an error-string argument is now an error. -
Legacy closures, and the
@parameterand@__copy_constructordecorators used to declare them, are deprecated and should not be used in new code. Use the newer closure syntax with capture lists, instead. Using these decorators doesn't currently generate a warning, because there are still a few APIs using these legacy closures. -
User-written structs must now explicitly declare closure-trait conformance in their inheritance list to satisfy a
def(...) -> ...closure trait. Previously, Mojo accepted a struct with a compatible__call__()implicitly (duck typing). Declare the trait in the struct's inheritance list:struct Double(def(Int) -> Int): # previously: `struct Double:`def __call__(self, x: Int) capturing -> Int:return x * 2Mojo checks conformance at the struct definition rather than deferring it to the use site.
-
A bare
**kwargsis now an error; writevar **kwargs(a fix-it inserts it) in function declarations and function types alike.varwas already the only supported convention—the sole exception to arguments defaulting toimm, applied silently before—so semantics are unchanged. -
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, as indef foo[x: Int]() where x > 0:. -
Struct fields can no longer hide
UnsafeAnyOrigin—a field such asvar ptr: Pointer[Int, MutUnsafeAnyOrigin]now errors. Mojo can't tell that uses of the enclosing struct contain anUnsafeAnyOrigin, so it doesn't do lifetime extension for values in its context. The typical solution is to add anOriginparameter, but you can also useUntrackedOriginif you explicitly manage the lifetime of the underlying data:struct Example[origin: Origin]:var ptr: Pointer[Int, Self.origin] -
Method
selfparameters must now have typeSelf; switch a customselftype to awhereclause.struct Foo[T: AnyType]:# ERROR: def foo(self: Foo[Int]):def foo(self) where Self.T == Int:... -
Mojo now rejects function overloads that differ only in argument convention (
immvsmut), as the compiler doesn't allow resolving overloads based on this. -
You can no longer use predefined and reserved words (for example,
class,del,match,yield) as the name of a free function. Doing so now errors at the declaration instead of silently producing a function that could never be called. -
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
Library stabilizations
We've begun the process of stabilizing the Mojo standard library. APIs marked stable won't be removed or changed in a way that breaks source compatibility.
We're being careful to stabilize only APIs that we can commit to, so the initial set of stable APIs is small, but we'll be adding to it in subsequent releases.
The following standard library types have one or more stable APIs in this release:
-
The
Deinitable,Movable,Copyable, andImplicitlyCopyabletraits, which are stable in their entirety.
Stable status is marked per-API in the API reference. For more information, see Mojo stability guarantees.
Library changes
Type system and traits
-
ImplicitlyDestructiblehas been renamed toDeinitable, for consistency with thedeinitargument convention and the__deinit__()spelling of the destructor. BothImplicitlyDestructibleand the intermediateImplicitlyDeletablespelling remain available as deprecated aliases. -
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. A by-index dual,reflect[T].field_at[idx], has also been added so a field's concrete type can be recovered while iterating fields by index, where the name is not available as a literal. -
The
ConditionalTypetype function instd.utils.type_functionsis now deprecated. Use the equivalent ternary expression instead:comptime Storage = Int if cond else NoneType. -
Erroris nowImplicitlyCopyable, so re-raising a caught error with a bareraise eno longer requires the transfer sigil. A capturedStackTraceis now reference counted, so copying anErrorcosts a reference count increment rather than duplicating the trace.raise e^still works and avoids the copy. -
The
Equatabletrait now allows positional-only implementations, and arguments on implementers no longer need to match the trait exactly.
Pointers and memory
-
The
PointerandUnsafePointertypes have been unified. The new unifiedPointertype includes the functionality ofUnsafePointer, with the unsafe operations prefixed withunsafe_or requiring anunsafe_-prefixed keyword argument. TheUnsafePointername and the unprefixed unsafe operations are deprecated.The two pointer types share the same layout and convert implicitly, so most code is unaffected. Raw-pointer APIs across the standard library now use the unified
Pointer.-
The pre-unification pointer aliases are deprecated in favor of the
Pointerfamily, and thetypeparameter ofPointeris renamed toT:Old spelling New spelling UnsafePointerPointerMutUnsafePointerMutPointerImmUnsafePointer,ImmutUnsafePointerImmPointerOptionalUnsafePointerOptionalPointer -
Code that calls pointer operations that are individually unsafe—unchecked bounds, aliasing casts, moving or overwriting memory—should switch to the
unsafe_*spelling, as shown below:Old spelling New spelling [i][unsafe_offset=i]ptr + iunsafe_offset(i)load()unsafe_load()store()unsafe_store()strided_load()unsafe_strided_load()strided_store()unsafe_strided_store()gather()unsafe_gather()scatter()unsafe_scatter()as_noalias()unsafe_as_noalias()address_space_cast()unsafe_address_space_cast()mut_cast()unsafe_mut_cast()take_pointee()unsafe_take_pointee()init_pointee_move()unsafe_write(value^)init_pointee_copy()unsafe_write(copy=value)destroy_pointee()unsafe_deinit_pointee()destroy_pointee_with()unsafe_deinit_pointee(closure)init_pointee_move_from()unsafe_write_move_from(src)free()unsafe_free()** Allocation should migrate to the layout-aware
memory.allocpackage.The previous unprefixed names still work, but are now hidden from the generated docs and issue a deprecation warning when called. Each method's docstring documents the exact
Safety:requirements the caller must uphold. -
The raw memory functions such as
memsethave also been renamed withunsafe_to make their unsafety explicit. The old names are deprecated and will be removed in a future release.
-
-
The implicit conversions that silently widened an
UnsafePointer's origin toUnsafeAnyOriginare going away.UnsafeAnyOriginis an unsafe escape hatch that silently extends unrelated lifetimes and disables exclusivity checking, so it should never be applied implicitly. The constructors that cast anUnsafePointertoMutUnsafeAnyOriginorImmUnsafeAnyOriginare now deprecated, and those that converted anUnsafePointerinto anOptional[UnsafePointer[..., UnsafeAnyOrigin]]have been removed. Prefer keeping a concrete origin; if you must discard it, make the cast explicit withas_unsafe_any_origin().Because origins are now preserved, 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(). And exclusivity checking now applies tounsafe_memcpy()(and similar) calls whosedestandsrcderive from the same buffer, so an intra-buffer copy that previously compiled now errors. Opt out by making one argument an unsafe any-origin—non-overlap ofdestandsrcis already anunsafe_memcpy()precondition:unsafe_memcpy(dest=buf + dst_off,src=(buf + src_off).as_unsafe_any_origin(),count=n,) -
Further renames, with the old names remaining as deprecated aliases:
Old spelling New spelling as_immutable(),get_immutable()as_imm()OwnedPointer.take()OwnedPointer.into_inner()StaticConstantOriginImmStaticOriginThe
as_imm()rename coversPointer,Span, andStringSpan, which previously spelled the same operation two different ways. -
AddressSpacehas moved fromstd.memory.pointertostd.memory.address_space. -
OwnedPointer.steal_data(),ArcPointer.steal_data(), andList.steal_data()have been renamed tounsafe_take_allocation()and now return an owningAllocationinstead of a raw pointer. The methods keep anunsafe_prefix because the elements are handed over still initialized: deallocating does not run their destructors. Recover the previous raw pointer withunsafe_leak().List.steal_data()andOwnedPointer.steal_data()remain as@deprecatedmethods, whileArcPointer.steal_data()is removed outright: the reconstructingArcPointer(unsafe_from_raw_pointer=...)constructor now takes a pointer to the control block (obtained fromunsafe_take_allocation().unsafe_leak()) and no longer accepts the payload pointer thatsteal_data()handed out. -
Pointernow supports subtracting two pointers to compute the signed distance between them in elements of the pointee type, via the newoffset_from()method; the-operator does the same. Unlike the other pointer-arithmetic operators, which produce a new pointer and stay gated behind an unsafe pointer type, subtracting two pointers returns anIntdistance and is available on safe pointers too.
Collections and iterators
-
InlineArrayhas been renamed toArray, its first parameter fromElementTypetoT, and its second parameter fromsizetolength. A temporaryInlineArraycomptime alias exists for adoption, and.sizeremains as a deprecated alias for.length. Update explicitInlineArray[ElementType=..., size=N]usages toArray[T=..., length=N]. -
Arrayno longer conforms toImplicitlyCopyable, since it is not inherently cheap to copy. It continues to conform toCopyable. -
Arrayis no longerDefaultable. Previously it conformed toDefaultablebut attempting to actually default construct anArraywould fail to compile. -
List,Span,String, andStringSpan(formerlyStringSlice) indexing with a contiguous (non-strided) slice now exits the program on an invalid slice instead of silently clamping it. For a slice to be valid:startandendmust each be in the range from 0 to container_length - 1.startmust be less than or equal toend.
Here, container_length is
len(container)forList/Span,string.byte_length()forbyte=indexed strings,string.count_codepoints()forcodepoint=indexed strings, andstring.count_graphemes()forgrapheme=indexed strings.var lst: List = [1, 2, 3]lst[0:100] # previously clamped to `lst[0:3]`; now abortslst[3:1] # previously returned an ill-defined result; now abortslst[:-1] # previously wrapped to `lst[0:2]`; now abortsSupport for negative indexes was removed in v1.0.0b1. The common "all but the last element" idiom must now spell the end index explicitly. Note that
lst[0 : len(lst) - 1]still aborts on an emptylst(0 : -1), so uselst[: max(len(lst) - 1, 0)]iflstmay be empty. -
Various types have adopted interior origins (described under language enhancements above), including
List,Deque,Variant,String,Dict,LinkedList,OwnedPointer, andHostBuffer. A reference or view into one of these containers now carries an interior origin, so the lifetime checker rejects one held across a mutation instead of letting it silently dangle after a reallocation. -
List.insert()andLinkedList.insert()no longer normalize negative indices. Mojo collections are moving away from negative indexing, so the valid index range is now[0, len(self)]; a negative index is out of bounds and aborts (checked when asserts are enabled). -
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(). -
Added
List.try_index(), which returns the index of a value in a list (if present) without raising, and is comptime-compatible. -
The
MutSpanandImmSpanaliases are now exported from the prelude, so they no longer need an explicit import fromstd.collections. This matches theMut/Immaliases forPointer, which the prelude already exported. -
Spannow has a keyword-onlyaddress_spaceparameter (defaulting toAddressSpace.GENERIC), so a span can view memory in a non-default address space, such as GPU shared memory. Address-only operations (indexing, slicing,unsafe_ptr(),as_imm(), and the SIMD search helpers) work in any address space and preserve it in their results, as doesfill()when the element type is register passable. The remaining element-copying operations (iteration,copy_from(), hashing, equality, and writing) are still restricted to the default address space. -
Span's pointer-and-length constructor argument is renamed fromptrtounsafe_ptr, to flag that this construction path is memory-unsafe: the caller must ensure the pointer addresses at leastlengthvalid elements. UpdateSpan(ptr=..., length=...)toSpan(unsafe_ptr=..., length=...). -
Spanhas moved fromstd.memory.spantostd.collections.span. -
Added
Dict.insert(key, value)andDict.clear_with(destroy_func), with mirroringSet.insert(element)andSet.clear_with(destroy_func), so aDictorSetwhose key, value, or element type is notDeinitablecan be populated and cleared. Unlikedict[key] = value,insert()doesn't destroy a displaced entry: it moves it out and returns it as anOptionalfor the caller to destroy.clear_with()hands each entry todestroy_funcand retains capacity. -
Dict.fromkeys(keys, value)has been generalized from taking aListto accepting any iterable of keys. Both forms require the key and value types to beDeinitable. -
By-reference
Dictiteration (for entry in dict,keys(),values(),items(), andreversed()) no longer requires the key and value types to beDeinitable. These iterators only borrow references and never destroy an entry, so they now work on aDictwhose key or value type is notDeinitable. Consuming iteration (for entry in dict^andtake_items()) still requiresDeinitable, since it drops the entries it does not yield. -
Variant.take[T]()andVariant.unsafe_take[T]()have been renamed toVariant.unwrap[T]()andVariant.unsafe_unwrap[T](). The old names remain as@deprecatedmethods and will be removed in a future release. -
Optionalno longer conforms toIterator; it is now anIterablecollection of 0 or 1 elements.for value in optandfor value in opt^are unchanged, but code that used anOptionaldirectly as an iterator (for example, callingnext()on it) no longer compiles and should iterate theOptionalinstead. -
BitSetgainedtest_range[bit_value: Bool, *, lo: Int, hi: Int], which efficiently tests that a bit range holds an expected value, and resizing constructors: theresized_from:keyword constructor zero-extends from a smaller set (and debug-asserts no set bits are dropped when shrinking), while a companion overload taking atruncate_set_bits: ()keyword argument truncates instead. -
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 can't take apart a tuple whose elements areMovablebut notImplicitlyCopyable;consume_elements()transfers ownership instead, mirroringVariadicPack.consume_elements().var t = ([1, 2, 3], [4, 5, 6]) # `List` is not `ImplicitlyCopyable`t^.consume_elements[handler]()
Explicitly-destroyed types and deinitialization
-
Several collection types now conditionally conform to
Deinitable, conforming only when their element type does. This lets a collection hold non-Deinitableelements at all (previously such a collection failed to compile); a collection of non-deinitable elements is itself explicitly destroyed and must be drained with the newdeinit_with()method, which calls a closure on each element:collection^.deinit_with(my_destroy_closure)For
Deinitableelement types—the common case—all of this is transparent, but generic code that takes one of these collections by value may now need& Deinitableadded to its element bound (as indef foo[T: Movable & Deinitable, //](var arr: Array[T, 3]):) so the collection can be dropped.Consuming iteration always requires
Deinitable, since it drops the elements it does not yield. Beyond that, the affected types differ in what a linear element still rules out:Type Operations that still require DeinitableelementsArrayNone. TupleNone, but tear a linear tuple down with deinit_with()orconsume_elements().DequeElement-destroying operations: append(),extend(),insert(),clear(),remove(), and so on.DictElement-destroying and key/value-copying operations: __setitem__(),setdefault(),fromkeys(),update(),pop(),clear().SetElement-mutating operations: add(),remove(),discard(),clear().LinkedListOnly clear().OwnedPointer[T]Conforms only when Tdoes; consume it withinto_inner()orunsafe_take_allocation().So a linear
DictorSetcan be constructed and torn down but not populated, while a linearLinkedListcan be populated too. TheSetelement bound also loosened fromKeyElement & Deinitableto justKeyElement, andLinkedList.insert()no longer raises on an out-of-range index; likeList.insert(), it now aborts (checked when asserts are enabled).Consuming iteration is conditional through the
IterableOwnedconformance; generic code bounded onIterableOwnednow rejects a non-conforming element type at the bound rather than failing later inside__iter__(). -
Array's element type bound loosened fromMovabletoAnyType, so anArraycan 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. -
OptionalandVariantnow accept element types that are notMovable. Their element types are now bounded byAnyType, withMovable,Copyable, and related conformances conditional on the element types. A non-Movablevalue can be stored in place with the new closure-basedinit_with=constructors andVariant.set()overload, which construct the value directly into storage (placement-new) rather than moving it.deinit_with()on both types also no longer requiresMovable, so element types that are neitherMovablenorDeinitableare fully usable:var opt = Optional[Pinned](call=make) # construct in placevar v = Variant[Pinned, Int](call=make)v.set(call=make) # replace in place -
The container backing variadic
**kwargshas been renamed fromOwnedKwargsDicttoStringDict.StringDictno longer requires its value typeVto beDeinitable. A keyword dictionary whose values are linear (non-Deinitable) 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 beDeinitable; useinsert(),popitem(), or the single-argumentpop(key)for linear values. -
You can now iterate over owned elements in
List,Dict,Array,LinkedList, andSetwhen the element type is notCopyable: theIterableOwnedconformance on these collections now requires onlyMovable & Deinitable, droppingCopyable. Afor var x in list^:loop therefore works for a non-Copyableelement type. -
Optionalgaineddeinit_assert_empty(), which destroys an empty linearOptionalwithout a caller-provided deinitializer, aborting in safe-assert builds if it is non-empty.Optional.map()andOptional.and_then()also now work when the element type is linear (notDeinitable): they move the contained value out and destroy the emptiedOptionalexplicitly, so a linear value can be transformed and handed back to the caller. -
is_trivially_destructible()has been renamed tois_trivially_deletable(). It now accepts any type (T: AnyType) instead of requiringT: Deinitable, returningFalsefor non-Deinitable(linear) types.
String and text
-
StringSlicehas been renamed toStringSpan, matching other non-owning view types such asSpan.StringSliceremains available as acomptimealias for the time being to ease transition to the new name.StringSpanhasMutStringSpanandImmStringSpanaliases, matching theMut/Immaliases already provided forSpanandPointer. The previousMutStringSliceandImmStringSlicenames remain available as compatibility aliases. -
Iterating over a
String,StringSpan, 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.
SIMD and numeric types
-
Intis now an alias forScalar[DType.int]and integer literals materialize to thisScalartype. Because of this, some conversions have become stricter. -
A new
SIMDLengthtype has been added for the length ofSIMDvectors. Use it when inferring a parameter from aSIMDargument, as indef frob[w: SIMDLength](v: SIMD[DType.int, w]): ...; leave the length unbound (SIMD[DType.int, _]) to be parametric over anySIMDtype, and useIntin all other situations. This type was briefly namedSIMDSizein nightly releases;SIMDSizeremains as a deprecated alias. -
The second parameter of
SIMDhas been renamed fromsizetolength, to match theSIMDLengthtype it is declared with and thelengthvocabulary the rest of the library uses for element counts. Positional uses such asSIMD[DType.float32, 4]are unaffected, and reading the parameter asv.sizestill works but warns. Binding it by keyword asSIMD[dtype, size=4]is an error and must be updated tolength=.The same standardization on
lengthoversizeapplies elsewhere. The old names remain as deprecated aliases where applicable:Old spelling New spelling ComplexSIMD[..., size=N]ComplexSIMD[..., length=N]TypeList.sizeTypeList.lengthDeviceContextList[size=N]DeviceContextArray[length=N]List.resize(new_size=, value=)List.resize(new_length=, fill=)List.shrink(new_size=)List.shrink(new_length=) -
range()has been reworked:- The
Int-based andScalar-based range types are unified into a singledtype-parameterized family, now thatIntisScalar[DType.int].range()withIntarguments behaves exactly as before. As part of this,range(...).__len__()always returnsInt, and asserts when an unsigned range's element count exceedsInt.MAXrather than silently clamping or wrapping; usebounds(), whose upper bound isNonein that case, for the size hint. - Floating-point iteration is now drift-free and reversible, so forward and
reverse iteration produce identical sequences across repeated calls and
across any IEEE-754 platform at the same width. Previously a step that was
not exactly representable, such as
0.1, could drift and yield an extra forward element thatreversed()then dropped. reversed()now works on typed ranges such asreversed(range(Int16(1), 10, 2)), because theReversibleRangetrait gained an associatedReversedTypeiterator instead of hard-coding its__reversed__()return type.- Non-numeric element types (
Booland the narrow MX float formats) are now rejected at construction, and 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.
- The
-
Any integer scalar can now be constructed from an
Intablevalue, not justInt, so taking a pointer's address as an unsigned integer works directly:UInt(p)rather thanUInt(Int(p)). -
repr()of a scalarSIMDvalue (length == 1) now prints using its type alias when the dtype has one, sorepr(UInt32(4))isUInt32(4)rather thanSIMD[DType.uint32, 1](4).length > 1values, and scalar dtypes without an alias (such asDType.bool), keep theSIMD[...]form.String(...)andprint(...)output is unchanged.
Python interoperability
-
The Python binding APIs now use safe pointers: the
PyCFunctionFastcalling convention used byPythonModuleBuilder.def_py_c_function()forMETH_FASTCALLcallbacks declares its argument array as aPointer[PyObjectPtr, MutUntrackedOrigin], typed-self methods registered throughPythonTypeBuilder.def_method()declare their self parameter as aPointer[Self](for example,self_ptr: Pointer[mut=True, Self]), and the extension argument helperscheck_and_get_arg()andcheck_and_get_or_convert_arg()return a safePointer. The pointer types share the same layout, so the C ABI and behavior are unchanged; update the spellings in signatures and read borrowed arguments withargs[unsafe_offset=i]. -
PythonObjectarithmetic, comparison, and membership operators now dispatch through CPython's abstract number, object, and sequence protocols (for example,PyNumber_Add,PyObject_RichCompare, andPySequence_Contains) instead of a Python-level attribute lookup followed by a bound-method call. Together with the non-mutating operators now borrowing their operand rather than taking it by value, this is roughly 12x faster on the interop hot path (a tighta + bora < bloop). It also follows standard Python operator semantics more closely, including reflected-operand fallback (__radd__,__rmul__, and so on) and the standard error messages for unsupported operations. An operation that no operand supports now raisesTypeError, where previously it could yield theNotImplementedobject as a value, and comparing mismatched types with==now returnsFalserather than a truthyNotImplemented. -
Added
copy_to_numpy_array()andfrom_numpy_array()to the newstd.python.numpymodule for moving flat numeric data between MojoSpan/Listand NumPy arrays without hand-writtenctypesplumbing. Both support the fixed-width numeric dtypes.copy_to_numpy_array()copies its input into a new, independent array;from_numpy_array()borrows the array's buffer zero-copy. -
Added
raise_python_exception()tostd.python.bindings, which translates a MojoErrorinto a Python exception viaPyErr_SetStringand returns a nullPyObjectPtr.
System, FFI, and runtime
-
OwnedDLHandlehas several changes to symbol lookup and calling:-
get_function()now 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") -
get_function()andcall()now forward arguments using the C ABI rather than the Mojo calling convention, so structs can be passed and returned by value. Multi-field struct arguments are no longer rejected at compile time. -
get_symbol()now returns a pointer that borrows the handle instead of one with an untracked origin, so the library can no longer bedlclosed while a resolved symbol is still live, and the_ = libkeep-alive is no longer needed. The pointer's mutability follows the handle's. -
The
cstr_nameoverload ofget_symbol()now takes aCStringSlicerather than aPointer[mut=False, Int8], so the nul-termination it requires is stated by the type instead of assumed. Drop theunsafe_ptr()afteras_c_string_slice()when calling it.
-
-
external_call()can now call C variadic functions. The new keyword-onlynum_fixed_argsparameter gives how many of the leading arguments are fixed arguments of the callee; the rest are passed as variadic arguments:# int open(const char *path, int oflag, ...);var fd = external_call["open", c_int, num_fixed_args=2](path_str.as_c_string_slice().unsafe_ptr(), c_int(flags), c_int(0o666))Left at its
Nonedefault, the callee is declared non-variadic, which miscompiles variadic calls on targets whose ABI passes variadic arguments differently from fixed ones. A count of0is distinct fromNone: it declares a callee whose every argument is variadic. -
Files opened through
open()with mode"w","rw", or"a"no longer have their permissions rewritten to0o666. A newly created file is now0o666 & ~umask(0o644under the commonumaskof022, matching Python), and an existing file keeps the permissions it already had. -
Added
runtime.initialize_runtime(), which initializes the Mojo runtime when Mojo code built as a shared library (mojo build --emit shared-lib) is called from a non-Mojo host program such as C or C++. In that situation no Mojomain()runs, so the runtime was never initialized and parallel or asynchronous APIs such asparallelize()crashed. Callinitialize_runtime()before using any runtime-dependent API; the call is idempotent and covers all threads in the process. See Call a Mojo shared library from C or C++ for details. -
chdir()has been added to thestd.osmodule and anfchdir()method has been added toio.FileDescriptor. These are wrappers for the corresponding POSIX functions.
Other library changes
-
Bencher.iter()now accepts a raising closure as a runtime argument, so a benchmark whose body raises can pass a closure with an explicit capture list instead of an@__parameterclosure. Prefer the unified closure form over the deprecated@__parameterone.def bench_add(mut b: Bencher) raises:@always_inlinedef call_fn() raises {var a, var c}:keep(a + c)b.iter(call_fn) -
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.
Package moves
-
Most standard library APIs related to accelerator programming have moved to a new
maxMojo package, including:std.benchmark.Bench.bench_multicontext->max.benchmark.bench_multicontextstd.benchmark.Bencher.iter_custom(DeviceContext)->max.benchmark.bencher_iter_customstd.gpu.compute->max.gpu.computestd.gpu.host->max.gpu.hoststd.gpu.memory->max.gpu.memorystd.gpu.sync->max.gpu.sync
-
The
layoutpackage is now bundled with MAX instead of Mojo.Some low-level APIs related to GPU programming remain in the standard library. For the latest GPU programming updates, see the MAX 26.5 changelog.
Tooling changes
-
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. -
Crash reporting now defaults to the
telemetry.enabledsetting, so the two are enabled or disabled together unless overridden. Settingcrash_reporting.enabled(or theMODULAR_CRASH_REPORTING_ENABLEDenvironment variable) explicitly still takes precedence. Previously crash reporting was disabled by default in one initialization path and enabled by default in production builds in another. -
The
program.crash_reporting_enabled_invocationtelemetry event has been renamed toprogram.initialized. It is emitted once per process whenever telemetry is enabled and carries acrash_reporting.enabledattribute recording whether crash reporting was on for that session. -
Added a
--fp-modeCLI flag that controls floating-point behavior as a comma-separated list of items. The only supported feature currently iscontract, one offast(default) oroff.contract=fastis like Clang's-ffp-contract=fast:a + b*ccan fuse into a fused multiply-add across statements, breaking strict IEEE compliance;contract=offdisables contraction for stricter floating-point semantics. -
Added a
--lld-pathCLI flag that overrides the LLD path Mojo uses.
Removed
-
Removed the
DType.invalidsentinel alias. Code that used it to represent an absent or optional dtype should useOptional[DType]instead. -
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 byStringandStringSpan. Use those keyword accessors instead (for example, on aStaticString). -
Removed the static
String.write()methods. Use the equivalentString()constructor instead, which accepts the sameWritablearguments (for example,String(a, b, sep=", ")instead ofString.write(a, b, sep=", ")). The memberwrite()methods that append to an existing string are unchanged. -
Removed
trait_downcast_var(). Improvements to type refinement based onwhere conforms_to(..)andcomptime assert conforms_to(..)make explicit value trait downcasting no longer necessary.
Fixed
-
#6485 -
Optional[T]andVariant[...]no longer corrupt data for payload types that include aBoolfield. -
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,T.property if conforms_to(T, HasProperty) else 0now compiles. -
Several cases involving constrained
comptimemembers andwhereclauses are now accepted rather than spuriously rejected: acomptimemember with a trailingwhereclause used as a witness for a conditional trait conformance that implies its constraint; a method whose return type references such a member, where the method's ownwhereclause discharges the constraint; and a method returning a generic struct whose parameter satisfies the struct's trait bound only through the method's ownwhereclause. -
A struct using
where Falseto opt out of a builtin trait's implicit synthesis (for example,Movable where False) no longer spuriously fails to compile when one of its fields also opts out of that same trait. -
#6740 - The reflection-based default
Equatableimplementation no longer fails to compile for single-elementRegisterPassablestructs. -
Closures mixing
*args, named keyword-only arguments, and**kwargsnow all work as values. A capturing closure taking**kwargsno longer fails to compile, and a call may now combine a*unpack, literal keyword arguments, and a**splat, as in Python:f(*args, **kwargs^)forwards both packed variadics directly, andf(1, named=2, **kwargs^)binds the literal keyword to its own named parameter alongside the splat. The reverse splat order (f(**kwargs, *args)) is rejected, matching Python, as is combining a**splat with other keyword arguments bound for the same**kwargs. -
#6755 - Volatile loads are no longer removed when their results are unused.
-
#6724 - The compiler now rejects invalid SIMD vector lengths during code generation.
-
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. Failed imports are no longer cached and may be retried, for example in the REPL. -
Mojo no longer imports struct extensions onto structs that happen to share a name with their intended struct when another struct shadows it. Given
from pkg_a import *followed byfrom pkg_b import Foo, the extensions defined bypkg_awere previously imported and callable on the unrelatedFoofrompkg_b. -
Importing a package whose name is a prefix of another package when split by dots (
import package_with, wherepackage_with.dots/is the real package) no longer works, and now errors. -
Importing escaped-identifier packages and modules whose names contain dots now works reliably, as in
from `package.with.dots`.`module.with.dots` import foo.mojo docand file-in-package builds also now use the whole dotted name for such packages, rather than truncating it at the first dot. -
#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. -
base64.b16decode()now raises on invalid input instead of silently producing corrupt output. -
CPython.PyCapsule_New()now takes itsnameargument as aStaticStringinstead of an ownedString. CPython stores thenamepointer directly in the capsule rather than copying it, so an ownedStringargument left the capsule holding a dangling pointer once the temporary was destroyed. -
#6727 -
mojo buildnow links libm, so a program calling a math function implemented by it—math.hypot(),math.expm1(), andmath.tanh()onFloat64, among others—builds successfully on Linux. Such a program previously ran fine undermojo runbut failed to link. -
debug_assert()generates less code, so builds with-D ASSERT=allcompile faster. Calls with no message arguments no longer allocate a 2048-byte message buffer in the caller's frame, which previously grew with the number of asserts and could push GPU kernels past the stack frame limit. A no-message assert failure now reportsassertion failedinstead of an empty message. -
Code completion now reports the correct completion kind for names bound by an unresolved
from module import namestatement; structs, traits, and functions imported this way previously completed with no kind at all. A renamed binding (from module import name as other_name) also no longer disappears from the completion list when another binding to the same declaration is in scope. -
Code folding in VS Code now works for Mojo files.
mojo-lsp-serverno longer advertises folding-range support, which only produced docstring ranges and caused VS Code to disable its built-in indentation-based folding. Editors now fall back to indentation-based folding until the server returns structural folding ranges.
Special thanks
Special thanks to our community contributors:
Adam Kruger (@lightofbaldr), Bernhard Merkle (@bmerkle), Christoph Schlumpf (@christoph-schlumpf), Danilo Salve (@odanilosalve), Gabriel de Marmiesse (@gabrieldemarmiesse), Giorgos Smyridis (@gsmyridis), Manuel Saelices (@msaelices), Rylan Malarchick (@rylanmalarchick), Sherlock Xu (@Sherlock113), SultanovAR (@SultanovAR)