IMPORTANT: To view this page as Markdown, append `.md` to the URL (e.g. /docs/manual/basics.md). For the complete Mojo documentation index, see llms.txt.
Skip to main content

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 lambda expressions—anonymous, single-expression closures that desugar to a nested def. List expressions now construct an Array rather than a List by default, and keyword variadics can be forwarded to another function with Python-style ** syntax. See Language enhancements.

  • Memory safety and pointer unification: Pointer and UnsafePointer are unified into a single Pointer type, with unsafety now marked on the individual operation instead of on the type as a whole. UnsafeAnyOrigin is 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 var is deprecated, a method's self must have type Self, and a bare **kwargs must be spelled var **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. size becomes length throughout, InlineArray becomes Array, StringSlice becomes StringSpan, ImplicitlyDestructible becomes Deinitable with the destructor spelled __deinit__(), and read becomes imm. Duplicated types are consolidated too: Int is now an alias for Scalar[DType.int], and the Int-based and Scalar-based range() types are unified into a single dtype-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 Deinitable only 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 new deinit_with() method. Optional and Variant also accept element types that are not Movable. See Explicitly-destroyed types and deinitialization.

  • Constraints do more of the work: a where clause 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 are Movable by default, with Movable 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_string produces 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, and size_of() returns the allocation size, fixing memory corruption for over-aligned types in List. See Collections and iterators, String and text, and SIMD and numeric types.

  • Faster Python interoperability: PythonObject arithmetic, 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 max Mojo package, and the layout package is now bundled with MAX instead of Mojo. Relatedly, Int and UInt no longer conform to DevicePassable and 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 as Int32 instead. See Package moves and the MAX 26.5 changelog.

Documentation

Language enhancements

  • Support for lambda expressions: anonymous, single-expression closures that desugar to a nested def. As in Python, the body is a single expression with no return; unlike Python, the arguments are parenthesized and typed as in a def signature—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 to None—so the bare lambda: expr is valid when expr is None-typed. These are fixed defaults, not inference (a non-None body still needs an explicit -> T).

    A thin (capture-free) lambda is a function value, exactly like a def referenced by name; any other lambda is 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 (with append() or pop(), 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 invalidated
    print(elem) # error: use of invalidated interior reference
  • Mojo now picks Array (instead of List) 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 Movable by default. To opt out of always-on movability, either explicitly specify a conditionally Movable conformance using Movable where <cond>, or opt out of Movable conformance entirely using Movable 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 -> None
    fp(42)
  • Mojo now supports == and != for type equality checks.

  • where clauses now accept an optional string-literal message, written where (condition, "message"). The compiler includes the message in the diagnostic when the constraint fails, and supports it everywhere where clauses are allowed: trailing function and struct constraints, struct conditional-conformance clauses, and alias/comptime declarations.

    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 direct conforms_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 trait JsonSerializable that inherits from Serializable, 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 inside where, comptime assert, and comptime if contexts:

    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 works
    args[i].write_to(writer)
  • Mojo now infers Trait for TypeList.of, such that:

    comptime TL = TypeList.of[Int, Bool]
    # works without
    comptime 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 argument

    For precompiled packages (.mojoc files), the compiler omits locations inside the package. It also doesn't report where std packages 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 inserts var:

    x = 0 # implicit declaration of 'x' is deprecated; add 'var' before the name
    var x = 0 # Fixed; no warning

    Every first assignment to a name warns, := walrus targets and a bare x: T annotation included. Binding forms that already spell out how they bind are unaffected: for targets, 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 .mojoc files, then source modules, then legacy precompiled .mojopkg files. Previously the order was unspecified.

    • Relative imports must use from (from . import foo); the import .foo form no longer works.

    • Absolute imports import a.b.c now bind all of a, a.b, and a.b.c into the scope, where previously only a.b.c was available. Two related bugs are fixed: import a followed by import a.b no longer errors with "invalid redefinition of 'a'", and function-scoped dotted imports (import a.b inside a function body) now work.

    • An imported package's submodules are now only accessible when the package's __init__.mojo re-exports them (for example, with from . 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, with from . 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 dir then dir.nested_dir.module.foo() is an error).

    • A standalone module can no longer import its own name (for example, import util inside util.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.

  • imm is now the preferred spelling for the read argument and closure-capture convention. read still 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 in List and other collections whose growth copies count * size_of bytes.

  • The @explicit_destroy decorator no longer opts a struct out of Deinitable conformance. Use Deinitable where False instead:

    struct ExplicitDestroy(Deinitable where False):
    def destroy(deinit self):
    pass

    You 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_destroy without an error-string argument is now an error.

  • Legacy closures, and the @parameter and @__copy_constructor decorators 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 * 2

    Mojo checks conformance at the struct definition rather than deferring it to the use site.

  • A bare **kwargs is now an error; write var **kwargs (a fix-it inserts it) in function declarations and function types alike. var was already the only supported convention—the sole exception to arguments defaulting to imm, applied silently before—so semantics are unchanged.

  • where clauses inside a parameter list (for example, [x: Int where x > 0]) are no longer supported, following a period of deprecation. Use a trailing where clause after the signature instead, as in def foo[x: Int]() where x > 0:.

  • Struct fields can no longer hide UnsafeAnyOrigin—a field such as var ptr: Pointer[Int, MutUnsafeAnyOrigin] now errors. Mojo can't tell that uses of the enclosing struct contain an UnsafeAnyOrigin, so it doesn't do lifetime extension for values in its context. The typical solution is to add an Origin parameter, but you can also use UntrackedOrigin if you explicitly manage the lifetime of the underlying data:

    struct Example[origin: Origin]:
    var ptr: Pointer[Int, Self.origin]
  • Method self parameters must now have type Self; switch a custom self type to a where clause.

    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 (imm vs mut), 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/comptime keywords and the following identifier.
    • Between the async and def keywords on function definitions.
    • Anywhere in the midst of an import statement, save for parenthesized import lists.

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:

Stable status is marked per-API in the API reference. For more information, see Mojo stability guarantees.

Library changes

Type system and traits

  • ImplicitlyDestructible has been renamed to Deinitable, for consistency with the deinit argument convention and the __deinit__() spelling of the destructor. Both ImplicitlyDestructible and the intermediate ImplicitlyDeletable spelling remain available as deprecated aliases.

  • The Reflected.field_type[name] reflection member has been renamed to Reflected.field[name], because it returns a chainable Reflected handle 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 .T member, as in reflect[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 ConditionalType type function in std.utils.type_functions is now deprecated. Use the equivalent ternary expression instead: comptime Storage = Int if cond else NoneType.

  • Error is now ImplicitlyCopyable, so re-raising a caught error with a bare raise e no longer requires the transfer sigil. A captured StackTrace is now reference counted, so copying an Error costs a reference count increment rather than duplicating the trace. raise e^ still works and avoids the copy.

  • The Equatable trait now allows positional-only implementations, and arguments on implementers no longer need to match the trait exactly.

Pointers and memory

  • The Pointer and UnsafePointer types have been unified. The new unified Pointer type includes the functionality of UnsafePointer, with the unsafe operations prefixed with unsafe_ or requiring an unsafe_-prefixed keyword argument. The UnsafePointer name 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 Pointer family, and the type parameter of Pointer is renamed to T:

      Old spellingNew spelling
      UnsafePointerPointer
      MutUnsafePointerMutPointer
      ImmUnsafePointer, ImmutUnsafePointerImmPointer
      OptionalUnsafePointerOptionalPointer
    • 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 spellingNew 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.alloc package.

      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 memset have also been renamed with unsafe_ 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 to UnsafeAnyOrigin are going away. UnsafeAnyOrigin is an unsafe escape hatch that silently extends unrelated lifetimes and disables exclusivity checking, so it should never be applied implicitly. The constructors that cast an UnsafePointer to MutUnsafeAnyOrigin or ImmUnsafeAnyOrigin are now deprecated, and those that converted an UnsafePointer into an Optional[UnsafePointer[..., UnsafeAnyOrigin]] have been removed. Prefer keeping a concrete origin; if you must discard it, make the cast explicit with as_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 explicit as_unsafe_any_origin(). And exclusivity checking now applies to unsafe_memcpy() (and similar) calls whose dest and src derive 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 of dest and src is already an unsafe_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 spellingNew spelling
    as_immutable(), get_immutable()as_imm()
    OwnedPointer.take()OwnedPointer.into_inner()
    StaticConstantOriginImmStaticOrigin

    The as_imm() rename covers Pointer, Span, and StringSpan, which previously spelled the same operation two different ways.

  • AddressSpace has moved from std.memory.pointer to std.memory.address_space.

  • OwnedPointer.steal_data(), ArcPointer.steal_data(), and List.steal_data() have been renamed to unsafe_take_allocation() and now return an owning Allocation instead of a raw pointer. The methods keep an unsafe_ prefix because the elements are handed over still initialized: deallocating does not run their destructors. Recover the previous raw pointer with unsafe_leak(). List.steal_data() and OwnedPointer.steal_data() remain as @deprecated methods, while ArcPointer.steal_data() is removed outright: the reconstructing ArcPointer(unsafe_from_raw_pointer=...) constructor now takes a pointer to the control block (obtained from unsafe_take_allocation().unsafe_leak()) and no longer accepts the payload pointer that steal_data() handed out.

  • Pointer now supports subtracting two pointers to compute the signed distance between them in elements of the pointee type, via the new offset_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 an Int distance and is available on safe pointers too.

Collections and iterators

  • InlineArray has been renamed to Array, its first parameter from ElementType to T, and its second parameter from size to length. A temporary InlineArray comptime alias exists for adoption, and .size remains as a deprecated alias for .length. Update explicit InlineArray[ElementType=..., size=N] usages to Array[T=..., length=N].

  • Array no longer conforms to ImplicitlyCopyable, since it is not inherently cheap to copy. It continues to conform to Copyable.

  • Array is no longer Defaultable. Previously it conformed to Defaultable but attempting to actually default construct an Array would fail to compile.

  • List, Span, String, and StringSpan (formerly StringSlice) 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:

    • start and end must each be in the range from 0 to container_length - 1.
    • start must be less than or equal to end.

    Here, container_length is len(container) for List/Span, string.byte_length() for byte= indexed strings, string.count_codepoints() for codepoint= indexed strings, and string.count_graphemes() for grapheme= indexed strings.

    var lst: List = [1, 2, 3]
    lst[0:100] # previously clamped to `lst[0:3]`; now aborts
    lst[3:1] # previously returned an ill-defined result; now aborts
    lst[:-1] # previously wrapped to `lst[0:2]`; now aborts

    Support 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 empty lst (0 : -1), so use lst[: max(len(lst) - 1, 0)] if lst may be empty.

  • Various types have adopted interior origins (described under language enhancements above), including List, Deque, Variant, String, Dict, LinkedList, OwnedPointer, and HostBuffer. 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() and LinkedList.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.capacity is now a capacity() 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 like append(). Replace my_list.capacity with my_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 MutSpan and ImmSpan aliases are now exported from the prelude, so they no longer need an explicit import from std.collections. This matches the Mut/Imm aliases for Pointer, which the prelude already exported.

  • Span now has a keyword-only address_space parameter (defaulting to AddressSpace.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 does fill() 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 from ptr to unsafe_ptr, to flag that this construction path is memory-unsafe: the caller must ensure the pointer addresses at least length valid elements. Update Span(ptr=..., length=...) to Span(unsafe_ptr=..., length=...).

  • Span has moved from std.memory.span to std.collections.span.

  • Added Dict.insert(key, value) and Dict.clear_with(destroy_func), with mirroring Set.insert(element) and Set.clear_with(destroy_func), so a Dict or Set whose key, value, or element type is not Deinitable can be populated and cleared. Unlike dict[key] = value, insert() doesn't destroy a displaced entry: it moves it out and returns it as an Optional for the caller to destroy. clear_with() hands each entry to destroy_func and retains capacity.

  • Dict.fromkeys(keys, value) has been generalized from taking a List to accepting any iterable of keys. Both forms require the key and value types to be Deinitable.

  • By-reference Dict iteration (for entry in dict, keys(), values(), items(), and reversed()) no longer requires the key and value types to be Deinitable. These iterators only borrow references and never destroy an entry, so they now work on a Dict whose key or value type is not Deinitable. Consuming iteration (for entry in dict^ and take_items()) still requires Deinitable, since it drops the entries it does not yield.

  • Variant.take[T]() and Variant.unsafe_take[T]() have been renamed to Variant.unwrap[T]() and Variant.unsafe_unwrap[T](). The old names remain as @deprecated methods and will be removed in a future release.

  • Optional no longer conforms to Iterator; it is now an Iterable collection of 0 or 1 elements. for value in opt and for value in opt^ are unchanged, but code that used an Optional directly as an iterator (for example, calling next() on it) no longer compiles and should iterate the Optional instead.

  • BitSet gained test_range[bit_value: Bool, *, lo: Int, hi: Int], which efficiently tests that a bit range holds an expected value, and resizing constructors: the resized_from: keyword constructor zero-extends from a smaller set (and debug-asserts no set bits are dropped when shrinking), while a companion overload taking a truncate_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 as a, b = t^ copies each element, so it can't take apart a tuple whose elements are Movable but not ImplicitlyCopyable; consume_elements() transfers ownership instead, mirroring VariadicPack.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-Deinitable elements 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 new deinit_with() method, which calls a closure on each element:

    collection^.deinit_with(my_destroy_closure)

    For Deinitable element types—the common case—all of this is transparent, but generic code that takes one of these collections by value may now need & Deinitable added to its element bound (as in def 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:

    TypeOperations that still require Deinitable elements
    ArrayNone.
    TupleNone, but tear a linear tuple down with deinit_with() or consume_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 T does; consume it with into_inner() or unsafe_take_allocation().

    So a linear Dict or Set can be constructed and torn down but not populated, while a linear LinkedList can be populated too. The Set element bound also loosened from KeyElement & Deinitable to just KeyElement, and LinkedList.insert() no longer raises on an out-of-range index; like List.insert(), it now aborts (checked when asserts are enabled).

    Consuming iteration is conditional through the IterableOwned conformance; generic code bounded on IterableOwned now rejects a non-conforming element type at the bound rather than failing later inside __iter__().

  • Array's element type bound loosened from Movable to AnyType, so an Array can now hold a non-Movable element type. The Movable conformance is now conditional on the element: move construction (including list-literal construction such as [a, b, c]) requires a Movable element, while indexing, by-reference iteration, and destruction do not. Code that uses Movable element types is unaffected, since a Movable element still yields a movable array.

  • Optional and Variant now accept element types that are not Movable. Their element types are now bounded by AnyType, with Movable, Copyable, and related conformances conditional on the element types. A non-Movable value can be stored in place with the new closure-based init_with= constructors and Variant.set() overload, which construct the value directly into storage (placement-new) rather than moving it. deinit_with() on both types also no longer requires Movable, so element types that are neither Movable nor Deinitable are fully usable:

    var opt = Optional[Pinned](call=make) # construct in place
    var v = Variant[Pinned, Int](call=make)
    v.set(call=make) # replace in place
  • The container backing variadic **kwargs has been renamed from OwnedKwargsDict to StringDict. StringDict no longer requires its value type V to be Deinitable. A keyword dictionary whose values are linear (non-Deinitable) is itself linear and must be torn down explicitly with the new deinit_with(deinit_func), which hands each key and value to deinit_func. It also gained insert(key, value) (returns the displaced entry as an Optional[DictEntry] without destroying it) and popitem() (moves out and returns a whole entry), mirroring Dict. Operations that destroy a displaced value in place—kwargs[key] = value and the two-argument pop(key, default)—still require V to be Deinitable; use insert(), popitem(), or the single-argument pop(key) for linear values.

  • You can now iterate over owned elements in List, Dict, Array, LinkedList, and Set when the element type is not Copyable: the IterableOwned conformance on these collections now requires only Movable & Deinitable, dropping Copyable. A for var x in list^: loop therefore works for a non-Copyable element type.

  • Optional gained deinit_assert_empty(), which destroys an empty linear Optional without a caller-provided deinitializer, aborting in safe-assert builds if it is non-empty. Optional.map() and Optional.and_then() also now work when the element type is linear (not Deinitable): they move the contained value out and destroy the emptied Optional explicitly, so a linear value can be transformed and handed back to the caller.

  • is_trivially_destructible() has been renamed to is_trivially_deletable(). It now accepts any type (T: AnyType) instead of requiring T: Deinitable, returning False for non-Deinitable (linear) types.

String and text

  • StringSlice has been renamed to StringSpan, matching other non-owning view types such as Span. StringSlice remains available as a comptime alias for the time being to ease transition to the new name.

    StringSpan has MutStringSpan and ImmStringSpan aliases, matching the Mut/Imm aliases already provided for Span and Pointer. The previous MutStringSlice and ImmStringSlice names remain available as compatibility aliases.

  • Iterating over a String, StringSpan, or StringLiteral now yields grapheme clusters by default. Their __iter__() and __reversed__() methods return a GraphemeSliceIter, so for 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() or codepoint_slices() for Unicode scalars, and bytes() for raw UTF-8 bytes.

SIMD and numeric types

  • Int is now an alias for Scalar[DType.int] and integer literals materialize to this Scalar type. Because of this, some conversions have become stricter.

  • A new SIMDLength type has been added for the length of SIMD vectors. Use it when inferring a parameter from a SIMD argument, as in def frob[w: SIMDLength](v: SIMD[DType.int, w]): ...; leave the length unbound (SIMD[DType.int, _]) to be parametric over any SIMD type, and use Int in all other situations. This type was briefly named SIMDSize in nightly releases; SIMDSize remains as a deprecated alias.

  • The second parameter of SIMD has been renamed from size to length, to match the SIMDLength type it is declared with and the length vocabulary the rest of the library uses for element counts. Positional uses such as SIMD[DType.float32, 4] are unaffected, and reading the parameter as v.size still works but warns. Binding it by keyword as SIMD[dtype, size=4] is an error and must be updated to length=.

    The same standardization on length over size applies elsewhere. The old names remain as deprecated aliases where applicable:

    Old spellingNew spelling
    ComplexSIMD[..., size=N]ComplexSIMD[..., length=N]
    TypeList.sizeTypeList.length
    DeviceContextList[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 and Scalar-based range types are unified into a single dtype-parameterized family, now that Int is Scalar[DType.int]. range() with Int arguments behaves exactly as before. As part of this, range(...).__len__() always returns Int, and asserts when an unsigned range's element count exceeds Int.MAX rather than silently clamping or wrapping; use bounds(), whose upper bound is None in 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 that reversed() then dropped.
    • reversed() now works on typed ranges such as reversed(range(Int16(1), 10, 2)), because the ReversibleRange trait gained an associated ReversedType iterator instead of hard-coding its __reversed__() return type.
    • Non-numeric element types (Bool and the narrow MX float formats) are now rejected at construction, and the one- and two-argument float ranges (range(Float64(4.5)) and range(Float64(0.5), Float64(3.0))) are compile errors instead of infinite loops; use the three-argument stepped form.
  • Any integer scalar can now be constructed from an Intable value, not just Int, so taking a pointer's address as an unsigned integer works directly: UInt(p) rather than UInt(Int(p)).

  • repr() of a scalar SIMD value (length == 1) now prints using its type alias when the dtype has one, so repr(UInt32(4)) is UInt32(4) rather than SIMD[DType.uint32, 1](4). length > 1 values, and scalar dtypes without an alias (such as DType.bool), keep the SIMD[...] form. String(...) and print(...) output is unchanged.

Python interoperability

  • The Python binding APIs now use safe pointers: the PyCFunctionFast calling convention used by PythonModuleBuilder.def_py_c_function() for METH_FASTCALL callbacks declares its argument array as a Pointer[PyObjectPtr, MutUntrackedOrigin], typed-self methods registered through PythonTypeBuilder.def_method() declare their self parameter as a Pointer[Self] (for example, self_ptr: Pointer[mut=True, Self]), and the extension argument helpers check_and_get_arg() and check_and_get_or_convert_arg() return a safe Pointer. The pointer types share the same layout, so the C ABI and behavior are unchanged; update the spellings in signatures and read borrowed arguments with args[unsafe_offset=i].

  • PythonObject arithmetic, comparison, and membership operators now dispatch through CPython's abstract number, object, and sequence protocols (for example, PyNumber_Add, PyObject_RichCompare, and PySequence_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 tight a + b or a < b loop). 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 raises TypeError, where previously it could yield the NotImplemented object as a value, and comparing mismatched types with == now returns False rather than a truthy NotImplemented.

  • Added copy_to_numpy_array() and from_numpy_array() to the new std.python.numpy module for moving flat numeric data between Mojo Span/List and NumPy arrays without hand-written ctypes plumbing. 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() to std.python.bindings, which translates a Mojo Error into a Python exception via PyErr_SetString and returns a null PyObjectPtr.

System, FFI, and runtime

  • OwnedDLHandle has 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 be dlclosed 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() and call() 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 be dlclosed while a resolved symbol is still live, and the _ = lib keep-alive is no longer needed. The pointer's mutability follows the handle's.

    • The cstr_name overload of get_symbol() now takes a CStringSlice rather than a Pointer[mut=False, Int8], so the nul-termination it requires is stated by the type instead of assumed. Drop the unsafe_ptr() after as_c_string_slice() when calling it.

  • external_call() can now call C variadic functions. The new keyword-only num_fixed_args parameter 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 None default, the callee is declared non-variadic, which miscompiles variadic calls on targets whose ABI passes variadic arguments differently from fixed ones. A count of 0 is distinct from None: 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 to 0o666. A newly created file is now 0o666 & ~umask (0o644 under the common umask of 022, 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 Mojo main() runs, so the runtime was never initialized and parallel or asynchronous APIs such as parallelize() crashed. Call initialize_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 the std.os module and an fchdir() method has been added to io.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 @__parameter closure. Prefer the unified closure form over the deprecated @__parameter one.

    def bench_add(mut b: Bencher) raises:
    @always_inline
    def call_fn() raises {var a, var c}:
    keep(a + c)

    b.iter(call_fn)
  • When an unhandled error propagates out of main and no stack trace was collected, Mojo now prints a hint to set MODULAR_DEBUG=stack-trace-on-error to 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 max Mojo package, including:

    • std.benchmark.Bench.bench_multicontext -> max.benchmark.bench_multicontext
    • std.benchmark.Bencher.iter_custom(DeviceContext) -> max.benchmark.bencher_iter_custom
    • std.gpu.compute -> max.gpu.compute
    • std.gpu.host -> max.gpu.host
    • std.gpu.memory -> max.gpu.memory
    • std.gpu.sync -> max.gpu.sync
  • The layout package 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-server no 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-docstrings when launching mojo-lsp-server from 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.enabled setting, so the two are enabled or disabled together unless overridden. Setting crash_reporting.enabled (or the MODULAR_CRASH_REPORTING_ENABLED environment 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_invocation telemetry event has been renamed to program.initialized. It is emitted once per process whenever telemetry is enabled and carries a crash_reporting.enabled attribute recording whether crash reporting was on for that session.

  • Added a --fp-mode CLI flag that controls floating-point behavior as a comma-separated list of items. The only supported feature currently is contract, one of fast (default) or off. contract=fast is like Clang's -ffp-contract=fast: a + b*c can fuse into a fused multiply-add across statements, breaking strict IEEE compliance; contract=off disables contraction for stricter floating-point semantics.

  • Added a --lld-path CLI flag that overrides the LLD path Mojo uses.

Removed

  • Removed the DType.invalid sentinel alias. Code that used it to represent an absent or optional dtype should use Optional[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 by String and StringSpan. Use those keyword accessors instead (for example, on a StaticString).

  • Removed the static String.write() methods. Use the equivalent String() constructor instead, which accepts the same Writable arguments (for example, String(a, b, sep=", ") instead of String.write(a, b, sep=", ")). The member write() methods that append to an existing string are unchanged.

  • Removed trait_downcast_var(). Improvements to type refinement based on where conforms_to(..) and comptime assert conforms_to(..) make explicit value trait downcasting no longer necessary.

Fixed

  • #6485 - Optional[T] and Variant[...] no longer corrupt data for payload types that include a Bool field.

  • Type refinement from a conforms_to() guard now applies inside the branches of a ternary exp1 if cond else exp2 used in a comptime context, matching the existing comptime if statement behavior. For example, T.property if conforms_to(T, HasProperty) else 0 now compiles.

  • Several cases involving constrained comptime members and where clauses are now accepted rather than spuriously rejected: a comptime member with a trailing where clause 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 own where clause discharges the constraint; and a method returning a generic struct whose parameter satisfies the struct's trait bound only through the method's own where clause.

  • A struct using where False to 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 Equatable implementation no longer fails to compile for single-element RegisterPassable structs.

  • Closures mixing *args, named keyword-only arguments, and **kwargs now all work as values. A capturing closure taking **kwargs no 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, and f(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.util failed to resolve, a later import util would silently bind the cached failure even when a real util.mojo exists 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 by from pkg_b import Foo, the extensions defined by pkg_a were previously imported and callable on the unrelated Foo from pkg_b.

  • Importing a package whose name is a prefix of another package when split by dots (import package_with, where package_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 doc and file-in-package builds also now use the whole dotted name for such packages, rather than truncating it at the first dot.

  • #4473 - The offset parameter of FileHandle.seek() (and NamedTemporaryFile.seek()) is now a signed Int instead of UInt64, so negative offsets relative to os.SEEK_CUR or os.SEEK_END work 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 its name argument as a StaticString instead of an owned String. CPython stores the name pointer directly in the capsule rather than copying it, so an owned String argument left the capsule holding a dangling pointer once the temporary was destroyed.

  • #6727 - mojo build now links libm, so a program calling a math function implemented by it—math.hypot(), math.expm1(), and math.tanh() on Float64, among others—builds successfully on Linux. Such a program previously ran fine under mojo run but failed to link.

  • debug_assert() generates less code, so builds with -D ASSERT=all compile 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 reports assertion failed instead of an empty message.

  • Code completion now reports the correct completion kind for names bound by an unresolved from module import name statement; 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-server no 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)