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.1.0

Highlights

  • Contextually inferred member references: a leading-dot form such as .red or .float64 now resolves against the expected type of the expression, so you can omit a redundant type name wherever context already supplies it. For example, you can use SIMD[.float64, 4] instead of SIMD[DType.float64, 4]. See Language enhancements.

  • Performance improvements: compile times and generated code both improve. Code performing many implicit conversions compiles faster, List growth is no longer quadratic, and t-strings both compile faster and emit smaller objects. See Language enhancements and Library performance improvements.

  • Continued migration to unified closures: the move from legacy closures (passed as compile-time parameters) to unified closures (passed as runtime arguments) carries on. More APIs now take their closures as runtime arguments, and the parameter forms are gone. On the language side, the @parameter decorator on parametric closures is now @__parameter, used only for declaring legacy closures. See Unified closures and Language changes.

  • Continuing library stabilizations: the deliberately small set of stable standard library APIs introduced in 1.0 grows again, adding signatures on String, SIMD, and List. See Library stabilizations.

  • Continuing cleanup: the deprecation cleanup begun in the 1.0 cycle completes. The legacy fn, alias, and __comptime_assert keywords and the @parameter if and @parameter for syntax are gone, along with the rename aliases, the redundant Int overloads, and the pre-unsafe_ spellings of the raw memory and pointer APIs. Support for .mojopkg files and the public async task API have also been removed. See Removed.

Documentation

  • Revised the quickstart: the install matrix is now a plain project-setup checklist, every section ends in a "Checkpoint" callout, and it now covers functions and error handling (try/except/else/finally).

  • Expanded the get-started tutorial with a 45-60 minute time estimate up front, guidance for readers coming from another language on using the Checkpoints to map unfamiliar syntax, clearer sample code, and concrete next steps.

Language enhancements

  • Mojo now supports contextually inferred member references: a leading-dot form such as .red or .float64 resolves against the expected type of the expression, so you can omit a redundant type name when context already supplies it. Static methods, parametric static methods, parentheses, attribute chains, and typed collection literals all work:

    struct Color(ImplicitlyCopyable):
    comptime red = Color(...)
    comptime green = Color(...)

    @staticmethod
    def hsb_to_rgb(h: Int, s: Int, b: Int) -> Color:
    return Color(...)

    def opacity(self, amount: Float64) -> Color:
    return Color(...)

    def __init__(out self, ...):

    def takes_color(c: Color):
    def takes_colors(colors: List[Color]):

    takes_color(.green)
    takes_color(.hsb_to_rgb(120, 100, 50))
    takes_color(.red.opacity(0.5))
    var x: Color = .red
    takes_colors([.red, .green])

    Without a contextual type, .member is an error.

  • The new @inline(value) decorator selects a function's inline level. Name a level with the new prelude InlineLevel struct: .always, .nodebug, .never, or .automatic, where nodebug matches @always_inline("nodebug") and also drops the inlined debug info. Spell the struct out, as in InlineLevel.never, where there is no contextual type to infer it from.

    The value need not be a constant: any comptime expression works, including a parameter, so one definition can be inlined or not per instantiation.

    @inline(.always)
    def doubled(x: Int) -> Int:
    return x * 2

    @inline(policy)
    def scaled[policy: InlineLevel](x: Int) -> Int:
    return x * 3

    def main():
    print(doubled(1) + scaled[.never](2))

    The compiler resolves a constant when it parses the decorator, and resolves a value that depends on a parameter once it binds that parameter.

  • Two inline decorators that disagree are now an error rather than one silently winning. @always_inline together with @no_inline previously compiled, picking whichever came first; write only the one you mean.

  • Unknown declaration errors now suggest a unique near-miss spelling from the enclosing scopes (for example councount), with a replace-token fixit.

  • A thin function type can now carry trailing where clauses, constraining the parameters it declares. This lets a generic algorithm state what it promises the function it is handed, instead of leaving every binding site to restate the constraint.

    comptime Kernel = def[w: Int](Int) thin -> None where (
    w > 0, "width must be positive"
    )

    def apply[F: Kernel](x: Int):
    F[4](x) # ok
    F[0](x) # error: violated constraint

    The clause binds to the innermost function type, so a declaration-level where that follows a function-type result needs that result parenthesized:

    def make[n: Int]() -> (def() thin -> None) where n > 0: ...
  • Code that performs many implicit conversions, most visibly large collection literals, compiles faster: the compiler no longer runs parameter inference on constructors that cannot be used for an implicit conversion in the first place. Files that are mostly data, such as the standard library's Unicode lookup tables, compile about 1.3x faster.

Language changes

  • Implicit variable declaration now produces an error instead of a warning. The walrus operator also only updates an existing variable—it doesn't implicitly declare a new one.

  • A walrus expression now always yields its right-hand side, uniformly for every kind of target.

  • Use of the read argument convention is now a hard error, following a period of deprecation; use imm instead.

  • Binding a constrained function to a function type that declares no matching where clause is now an error, instead of silently dropping the constraint. Declare the obligation on the function type (now that a thin function type can carry a trailing where clause) or bind a function that does not require it. Passing an unconstrained function where a constrained type is expected is still allowed and still free.

  • Renamed the @parameter decorator on parametric closures to @__parameter, which is now used only for declaring legacy closures. Removed the @parameter if and @parameter for forms; use comptime if and comptime for for compile-time control flow.

  • The module and package system:

    • Directories may now have "namespace" semantics; a single directory name may resolve across distinct locations on disk that share that name.

      # .
      # ├── one
      # │   └── foo
      # │      └── bar.mojo
      # └── two
      #    └── foo
      #       └── baz.mojo
      #
      # Compiles with -Ione -Itwo
      import foo.bar
      import foo.baz
    • Importing functions with the same name from different modules, combining them into one overload set, is now an error, following a period of deprecation.

    • Intra-package access without an explicit import statement is now an error, following a period of deprecation.

Library stabilizations

  • String

    • def __init__(out self):
    • def __init__(out self, *, capacity_bytes: Int):
    • def reserve_bytes(mut self, new_capacity_bytes: Int, /):
  • SIMD

    • def __init__(out self):
    • def __eq__(self, rhs: Self) -> Bool:
    • def __len__(self) -> Int:
  • List

    • def append(mut self, var value: Self.T, /):

Library performance improvements

  • List.extend() and List.resize() now grow geometrically, so repeatedly extending or resizing by a small increment is no longer quadratic. As a result, capacity() can report more than you requested. reserve() is unchanged and still allocates exactly what you request.

  • Files with many t-string literals (t"...") compile faster: the compile-time step that encodes each literal's format string (part of elaboration, not the whole compile) is about 7x faster. The effect on total build time scales with how many t-string literals a file has.

  • TString no longer carries its format string as a parameter, so distinct t-string literals share one specialization instead of emitting a function each, and the message-formatting paths of debug_assert() and abort() are now out of line. A file with 2,500 distinct literals emits 14 functions instead of 2,510, shrinking its object by about 85%.

Library changes

Unified closures

Many more APIs have migrated from legacy closures (passed as parameters) to unified closures (passed as arguments).

  • Migrated the following APIs to unified closures: sort(), debug_assert(), and Span.apply().

  • std.algorithm's tile(), unswitch(), tile_and_unswitch(), and tile_middle_unswitch_boundaries() now take their workgroup function as a trailing closure argument instead of a legacy closure parameter, as in tile[sizes](offset, bound, workgroup_function=body).

  • The benchmarking APIs now take unified closures instead of legacy closures, and the parameter forms are gone:

    • Bench.bench_function() now takes a raising closure.

    • The formerly zero-argument Bench.bench_function() overload now takes a raising closure as a runtime argument. Removed the compile-time parameter form bench_function[fn]() for a raising zero-argument body.

    • Removed the remaining compile-time parameter forms of Bench.bench_function() and Bencher.iter(). Pass the closure as a runtime argument.

    • Bencher.iter_preproc() now takes its closures as runtime arguments instead of compile-time parameters, along with an explicit state value that it passes mutably to both: the preprocessing function prepares the state before each timed call of the benchmarked function, so you no longer have to shuttle state through mutable captures.

    • Bench.bench_with_input() now takes its benchmark closure as a runtime argument. Its register-passable overload accepts both non-raising and raising closures.

    • Bencher.iter_custom() now takes its closure only as a runtime argument. Removed the compile-time parameter form.

Collections

  • Array now conforms to Comparable when its element type does, adding <, <=, >, and >=. The ordering is lexicographic: the first differing element decides, so [1, 5] < [2, 3] is True.

  • Array now conforms to Defaultable when its type T is also Defaultable.

  • Array now supports concatenation with the concat() method when its type T is Movable. concat() consumes both operands and moves their elements into the new array, whose length is the sum of the operands' lengths.

  • Array now supports repetition with the repeat() method when its type T is Copyable. repeat() consumes the array: it copies the elements into all but the last repetition and moves them into the last one.

  • Array[T, N] has a new fill_with= constructor that calls a function with each index in [0, N) and writes its result into that position, replacing the Array(uninitialized=True) plus manual fill-loop idiom.

  • List's element type is now bounded by AnyType instead of Movable.

  • List has a new fill_with= constructor that calls a function with each index in [0, length) and writes its result into that position, without requiring the element type to be Movable.

  • StringDict now conforms to Writable when its value type is Writable, matching the existing behavior of Dict. This lets you print() a StringDict or convert it to a String.

  • StringDict.__getitem__() now accepts a StringSpan, so you can index a StringDict with a borrowed string view without first allocating a String just to perform the lookup.

  • You can now construct a Counter from any iterable of values, not just a List, for example Counter(["a", "a", "b"]) or Counter(String("aaab").bytes()). This replaces the previous Counter(items: List[V]) constructor.

Pointer and memory

  • Deprecated is_trivially_movable(), is_trivially_copyable(), and is_trivially_deletable() in std.memory in favor of IsTriviallyMovable[T], IsTriviallyCopyable[T], and IsTriviallyDeinitable[T] in std.traits. The replacements are comptime predicates rather than functions, so drop the call parens at use sites, for example IsTriviallyCopyable[T] instead of is_trivially_copyable[T]().

  • Renamed UnsafeMaybeUninit to MaybeUninit. It conforms to Movable, Copyable/ImplicitlyCopyable, and Deinitable only when the contained type is trivially movable, copyable, or deinitable, since moving, copying, or destroying a MaybeUninit only touches its raw bits, never the contained value's own lifecycle methods. Gating conformance this way turns what would otherwise be silent memory-safety bugs into compile-time errors.

  • Added a deinit() free function for any Deinitable type, to explicitly extend a value's lifetime up to a specific point and run its deinitializer there.

  • Added Pointer[T].unsafe_write(def() -> T), which initializes the pointee with the value returned by a closure, constructing it directly in place rather than moving an already-constructed value there. Unlike unsafe_write(var T), this does not require the pointee type to be Movable.

  • Added write() to MaybeUninit and Pointer, as a safe counterpart to unsafe_write() for types that are trivially deinitializable (for example Int). Since a trivial deinitializer is a no-op, overwriting a live value through write() can't leak a resource, so it's callable without first destroying the previous value. Prefer it over unsafe_write() whenever the pointee type is trivially deinitializable.

  • Deprecated Pointer.mut_cast(). Prefer explicit mutabilities at the call site, using MutPointer or ImmPointer. Where a mut cast is unavoidable, use unsafe_mut_cast().

  • Renamed CStringSlice to CStringSpan, matching the Span-based naming of the other non-owning view types (StringSpan, Span). The old CStringSlice name remains available as a compatibility alias. Likewise, as_c_string_slice() is now as_c_string_span().

  • Added ptr() to StringLiteral, CStringSpan, ArcPointer, and OwnedPointer, deprecating their unsafe_ptr() methods. These types always hold a valid, live value, so a pointer to it is never unsafe.

Traits and type system

  • OwnedDLHandle now conforms to Boolable.

  • Hasher.update() now takes an ImmSpan[Byte, _] instead of Some[Hashable]. Change code such as hasher.update(some_subobject) in __hash__ implementations to some_subobject.__hash__(hasher).

  • Renamed the variadic type-list parameter on Tuple and VariadicPack to Ts, standardizing the naming convention used across the standard library. The old name, element_types, remains as a deprecated alias.

  • Atomic is now parameterized on a value type T instead of a DType. Update call sites from Atomic[DType.float32] to Atomic[Float32]. The atomic operations (load(), store(), fetch_add(), compare_exchange(), and so on) still only support Scalar types.

  • Renamed any_satisfies() and all_satisfies() on TypeList and ParameterList to any() and all().

Compilation targets

  • CompilationTarget has a new is_arm() predicate, and is_x86() now reports the architecture rather than SSE4 availability. Both read the architecture from the target triple, so they no longer vary with --target-cpu. This changes is_x86() on x86 targets without SSE4.1—most visibly the baseline x86-64 CPU, where it used to return False. Use has_sse4(), has_avx2(), and friends to gate code on a specific instruction set.

  • CompilationTarget can now describe RISC-V targets: is_riscv(), is_rv32(), and is_rv64() report the architecture, and has_riscv_extension["m"]() reports a single ISA extension by its lowercase LLVM name. An extension implied by another counts as present, so a target built with d also reports f. It is always False on a non-RISC-V target, and rejects an uppercase name at compile time.

    Selecting a RISC-V CPU or ISA string now resolves the extensions it implies, so --target-cpu=sifive-e31 and --march=rv32imac both report m, a, and c. Previously either one reported only the base integer ISA.

Python interoperability

  • Python functions exposed through PythonModuleBuilder.def_function(), PythonTypeBuilder.def_method(), and PythonTypeBuilder.def_staticmethod() no longer have a library-imposed limit on positional arguments.

  • std.python.numpy now handles multi-dimensional NumPy arrays, not just 1-D:

    • copy_to_numpy_tensor() copies a Span into a new NumPy array of a given shape. The shape is a Coord, so extents may be compile-time (Idx[N]) or runtime (Int) in any mix.

    • from_numpy_tensor() borrows an N-D C-contiguous array as a NumPyView, which holds the buffer and its shape together and indexes as view[i, j].

    from std.python.numpy import copy_to_numpy_tensor, from_numpy_tensor
    from std.utils.coord import Coord, Idx

    var values: List[Float64] = [0, 1, 2, 3, 4, 5]
    var arr = copy_to_numpy_tensor(values, Coord(Idx[2], Idx[3]))

    var view = from_numpy_tensor[DType.float64, 2](arr)
    var value = view[1, 2]

    The existing 1-D copy_to_numpy_array() and from_numpy_array() are unchanged.

Other library changes

  • The simd module moved from std.builtin to the top level of std, so SIMD and its aliases now live in std.simd. Nothing changes for code that relies on the prelude; an explicit from std.builtin.simd import ... becomes from std.simd import ....

  • Coord has a new replace[at](value) method that returns a Coord with the element at at swapped for value, keeping the other elements' types. A statically known element (ComptimeInt) has no runtime storage to assign into, so overwriting one with a runtime value yields a Coord of a different type rather than mutating in place. Unlike make_dynamic(), which converts every element to a Scalar, the untouched dimensions keep their compile-time values:

    var c = Coord(ComptimeInt[3](), ComptimeInt[4]())
    var moved = c.replace[1](Int64(7)) # Coord(ComptimeInt[3](), Int64(7))
  • The chars argument of strip(), lstrip(), and rstrip() on StringSpan, String, and StringLiteral is now an ImmStringSpan, so chars now accepts a mutable string, including the string being stripped (s.strip(s)).

  • Added experimental DType.float6_e2m3fn and DType.float6_e3m2fn, the two 6-bit encodings from the Open Compute microscaling specification. Both are finite-only, so neither has an inf or NaN encoding.

    These are experimental storage formats for packed weights rather than general-purpose numeric types, and standard library support is deliberately partial. As with the existing DType.float4_e2m1fn, they are excluded from is_numeric(), arithmetic is not implemented, and converting to or from another floating-point type is unsupported on every target, so values cannot be printed either.

  • Uncaught exceptions now print to stderr, not stdout.

  • You can now construct a Coord from an Array[Scalar[dtype], rank], mirroring the existing IndexList constructor. An Array carries no compile-time extents, so the result is an all-dynamic Coord.

GPU programming

  • The max.gpu package now mirrors everything reachable from std.gpu, making it a complete entry point for accelerator programming, and the std.gpu package is now private, as std._gpu. max.gpu is the only public source for the GPU primitives, and its API reference is the only published one; /docs/std/gpu/... pages redirect to /api/mojo/max/gpu/.... Replace from std.gpu import ... with from max.gpu import ...; a failed std.gpu import carries a note pointing at the new home.

Tooling changes

  • mojo doc now reports the condition of a conditional trait conformance, and the generated API docs show it alongside the trait. Previously mojo doc dropped the condition, making a conditional conformance indistinguishable from an unconditional one. Also fixed rendering of some where clauses.

  • The new @__doc_inline decorator on an import statement documents the imported symbols in the importing module, so a package can document an API it re-exports from private modules. The decorator does not support wildcard (import *) or renamed (import ... as ...) imports.

    @__doc_inline
    from ._impl import Widget, make_widget
  • mojo build and mojo run can now report where a compile spends its time. --mlir-timing times every MLIR pass and analysis, and --llvm-timing does the same for LLVM, each printing a report to stderr when the compilation finishes, which for mojo run is before the program starts. --mlir-timing-display groups the MLIR report as a tree (the default), which nests by pipeline structure, or as a list, which aggregates by pass name and sorts by total time. These options are hidden; use --help-hidden to list them.

    Two things shape what the numbers mean. First, --llvm-timing pins the compile to one thread and overrides --num-threads, because LLVM's timers are global to the process and are not thread safe, so its report measures the work LLVM does rather than the cost of a parallel build. Second, passes served from the compilation cache never run, so a warm cache reports little and an object cache hit leaves the LLVM report empty; point MODULAR_CACHE_DIR at an empty directory to time a whole pipeline.

  • --timing-json emits both timing reports as JSON, and --timing-file writes them to a file. The two are independent, so the text reports can go to a file and the JSON can go to stderr.

    The JSON is one object per command, holding an mlir member, an llvm member, or both, depending on which timing the command asked for. A command that asks for JSON but for no timing writes {}, so a consumer can parse the output without first checking which timing options ran.

Removed

This release completes the removal of APIs deprecated during the v1.0 cycle.

  • Removed the legacy constructs replaced in 1.0, including the fn, alias, and __comptime_assert keywords and the @parameter if and @parameter for syntax.

Deprecated aliases and renamed APIs

  • Removed the temporary InlineArray alias for Array, including its re-exports from std.collections and the prelude. Use Array directly.

  • Removed the origin aliases left over from the Immut to Imm and External to Untracked renames. Use the surviving spelling in each case: ImmOrigin for ImmutOrigin, ImmUnsafeAnyOrigin for ImmutUnsafeAnyOrigin, ImmStaticOrigin for StaticConstantOrigin, UntrackedOrigin for ExternalOrigin, MutUntrackedOrigin for MutExternalOrigin, and ImmUntrackedOrigin for both ImmutUntrackedOrigin and ImmutExternalOrigin.

  • Removed the pre-unification pointer aliases MutUnsafePointer, ImmUnsafePointer, ImmutUnsafePointer, ImmutOpaquePointer, ImmutPointer, and OptionalUnsafePointer. Use MutPointer, ImmPointer, ImmOpaquePointer, and OptionalPointer instead. UnsafePointer itself remains available, but is deprecated in favor of Pointer.

  • Removed the size aliases left from the size to length rename: SIMD.size, Array.size, TypeList.size, and the SIMDSize alias for SIMDLength. Use length and SIMDLength.

  • Removed the as_immutable() and get_immutable() methods on Pointer, Span, and StringSpan. Use as_imm().

  • Removed the ImmutSpan alias. Use ImmSpan.

  • Removed String.as_string_slice(). Construct a StringSpan from the string instead: StringSpan(my_string).

  • Removed the ImplicitlyDestructible and ImplicitlyDeletable aliases. Use Deinitable.

  • Removed the deprecated ownership-transfer methods: List.steal_data() and OwnedPointer.steal_data() are now unsafe_take_allocation(), OwnedPointer.take() is into_inner(), and Variant.take() and Variant.unsafe_take() are unwrap() and unsafe_unwrap().

Redundant Int overloads

  • Removed redundant Int overloads across the standard library. Int is an alias for Scalar[DType.int], so the generic SIMD overloads already accept Int arguments and return Int; call sites need no changes.

    • count_leading_zeros(), count_trailing_zeros(), bit_reverse(), byte_swap(), pop_count(), log2_ceil(), next_power_of_two(), and prev_power_of_two() in std.bit; readfirstlane() in std.sys; and umod() in std.math.uutils.

    • rotate_bits_left() and rotate_bits_right() in std.bit. The SIMD overloads now accept any integral element type instead of only unsigned ones—rotation is a pure bit-pattern operation, so signed and unsigned rotate identically—and therefore handle Int arguments directly.

    • sqrt(), fma(), align_down(), align_up(), clamp(), and iota() from std.math.

Unsafe-prefixed replacements

  • Removed the APIs superseded by their unsafe_-prefixed spellings:

    • memcmp() and its std.memory re-export. Use unsafe_memcmp() instead.

    • The raw memory functions memcpy(), memset(), memset_zero(), uninit_move_n(), uninit_copy_n(), and destroy_n(). Use unsafe_memcpy(), unsafe_memset(), unsafe_memset_zero(), unsafe_uninit_move_n(), unsafe_uninit_copy_n(), and unsafe_destroy_n() instead.

    • The Pointer methods as_noalias_ptr(), destroy_pointee(), destroy_pointee_with(), init_pointee_move(), init_pointee_copy(), and init_pointee_move_from(). Use unsafe_as_noalias(), unsafe_deinit_pointee(), unsafe_deinit_pointee_with(), unsafe_write(), and unsafe_write_move_from(). The Pointer.type alias for Pointer.T is gone as well.

Async APIs

  • Removed AnyCoroutine, Coroutine, and RaisingCoroutine from the prelude, and made the module that defines them private. Mojo's async support is unfinished, and their global visibility led people to build on an API that carries no stability guarantees. async def is unaffected: the compiler still synthesizes these types for you, so they continue to appear in inferred types and diagnostics. There is no supported way to name them directly.

  • Removed the async task API from the public std.runtime.asyncrt module, which is now private. initialize_runtime() and parallelism_level() are unaffected and have moved up to the std.runtime package, so import them from std.runtime instead of std.runtime.asyncrt.

Other removals

  • Removed String.set_byte_length(), an internal helper that set the length field without reserving capacity.

  • Removed the validate parameter from b64decode(), which now always validates. Passing validate=False did not skip any work on valid input; it only turned characters outside the base64 alphabet into silently corrupt output bytes. Drop [validate=True] from existing calls; calls that relied on the default now raise instead of returning garbage.

  • Removed the ConditionalType type function and the std.utils.type_functions module. Use the ternary expression T if cond else U.

  • Removed trait_downcast(). Constrain on the trait instead, with conforms_to(type_of(src), Trait) in a where clause or a comptime assert.

  • Removed the parametric benchmark.run[func]() overloads. Instead, pass the function as an argument to run(f), which accepts a unified closure.

  • Removed support for .mojopkg files after a period of deprecation. Use .mojoc files instead.

Fixed

Compiler and comptime

  • The compiler can now prove a where clause naming a type that an enclosing where clause constrained to a tighter trait. Calling a method declared where Ts.contains[T]() with such a T failed with lacking evidence to prove correctness, even though T was plainly in Ts.

  • Parametric raises now accepts any primary expression as the thrown type in a function signature, matching the syntax positions where types otherwise appear. This most notably fixes raises Self.SomeAssocType on trait and struct methods, which previously failed with an error. The parenthesized workaround (raises (Self.DriveErrorType)) is no longer required.

  • A spurious attempt to resolve a recursive reference to declaration error no longer fires on valid code. Resolving the signature of a trait method inherited from a parent trait no longer forces the parent's default body to resolve, so a default whose body reaches a type conforming to the inheriting trait no longer forms a resolution cycle.

  • The compiler no longer treats an implicit conversion whose constructor candidate it can neither prove nor disprove in the asking scope as a definitive rejection, so a conversion that depends on a parametric constraint alias now resolves once the constraint is known.

  • Origin inference now works through an implicit constructor that binds its operand by ref. Passing an rvalue to a function taking an origin-parameterized type, as in take_wrapper(a + b) where the implicit __init__ takes ref[origin] value, materializes a temporary and infers its origin instead of failing.

  • to_layout_tensor() no longer hangs the compiler on a tensor with a nested layout. The type-only coord_to_int_tuple now recurses on a nested Coord's own element types, and to_layout_tensor() flattens shape and stride so nested layouts get one entry per leaf; flat layouts are unchanged.

  • The compiler no longer crashes while printing a parameter list that contains a positional variadic bound to a single type, so calls such as helper(Bag[Leaf, tail=1]()) now report a proper conversion error.

  • Recursive or excessively deep comptime call graphs no longer crash the compiler. Parameter-expression inlining now detects cycles and bounds its recursion depth, so an unbounded expression such as f[n] calling f[n + 1] reports an error instead of overflowing the stack.

  • Forwarding a VariadicPack that came from somewhere other than a call site, such as one returned from a function declared -> VariadicPack[..], no longer crashes the compiler. Origin tracking did not handle a pack it had not itself constructed.

  • A comptime call that names its callee concretely no longer aborts in the compile-time interpreter. The compiler looked the callee up by name without checking that it had finished elaborating, so the interpreter received a body still holding unresolved parameters.

  • The compiler no longer folds a loop result that was only constant on some paths to that constant. Constant propagation could conclude a loop result was known while it had not yet analyzed some of the loop's break or continue edges, producing a value only one path actually computed.

Numerics and SIMD

  • #6850 - SIMD.__init__(py=...) now reads unsigned dtypes through the unsigned CPython entry point (PyLong_AsSize_t). Constructing an unsigned SIMD from a Python int in [2**63, 2**64) no longer overflows, and a negative Python int now raises instead of silently wrapping to the maximum value.

  • #6921 - A union whose widest member is a SIMD[DType.bool, N] with N > 1, such as Optional[SIMD[DType.bool, 2]], now compiles.

  • #6851 - hash() on a floating-point SIMD value now normalizes the sign of zero, so hash(-0.0) == hash(0.0). Hashing the raw bit pattern broke the Hashable contract that equal values hash equally: a Dict or Set could hold both -0.0 and 0.0 as separate keys even though they compare equal, and a lookup could then return a value stored under the other key.

  • Fixed ceildiv() returning 0 for unsigned operands near the type's maximum value. The unsigned code path computed numerator + denominator - 1, which overflows and wraps for large operands; it now derives the ceiling from the floor division and remainder instead.

  • sqrt(), rsqrt(), pow(), sin(), cos(), log10(), and log1p() now work on Apple GPUs. Mojo now widens narrow floats such as bfloat16 to float32 around the underlying Metal intrinsic, which has no bfloat16 overload, and log1p() no longer evaluates its polynomial in float64, a type Metal does not have at all; that previously surfaced as an LLVM verifier abort rather than a diagnostic.

Memory and pointers

  • Destroying an OwnedDLHandle that holds a null handle no longer crashes the process.

  • unsafe_uninit_move_n() and unsafe_uninit_copy_n() with overlapping=True now handle an overlap in either direction when T is not trivially movable or copyable. They always walked front-to-back, so a dest above src overwrote elements that had not been moved or read yet.

  • Every value of a struct type whose @align(N) exceeds its natural alignment is now aligned to N, including every element of an array or a List of that type.

  • unsafe_memcpy() now chunks GPU copies by register width. It passed a bit width where an element count belonged, making each chunk eight times too wide; NVPTX then gave up on vectors and copied byte by byte. Error-reporting code, which does most of the standard library's copying, shrinks substantially as a result.

Tooling and build

  • mojo build can cross-compile to RISC-V again. Emitting LLVM IR, assembly, or an object for a riscv32 or riscv64 triple failed with target '...' is not supported by this build.

  • mojo build --print-supported-targets no longer lists targets that the compiler cannot generate code for.

  • mojo build --emit asm and --emit llvm now always write the offload kernel files next to the host output file. Building a kernel that an earlier build had already compiled could write them into the earlier build's output directory, or skip them with no diagnostic.

  • Importing a precompiled package now resolves that package's own recorded dependencies from the importing file's location, so the compiler again finds a dependency that lives beside the main file.

Ranges and iteration

  • An integer range() with a step of zero is now always empty. It previously produced an infinite loop, iterating forever at runtime and hanging the compiler at comptime.

  • A strided range() no longer iterates forever when the element after the last one falls outside the element type, as in range(UInt8(250), UInt8(255), UInt8(2)). The cursor used to wrap past the type's limit and land back inside the range, so iteration restarted near the opposite limit and never agreed with len(). This affected signed and unsigned ranges in both step directions.

  • reversed() on a scalar range() no longer yields an empty iterator when the range starts within one step of the element type's limit, as in reversed(range(Int8.MIN, Int8.MIN + 8, Int8(1))). The same change fixes unsigned ranges, and ranges whose span overflows their element type.

    Reversing an already-reversed range, as in reversed(reversed(range(10))), is now a compile-time error.

Strings and encoding

  • #6831 - base64.b64decode() now raises an error when the input length is not divisible by 4 instead of reading past the end of the input (or aborting when asserts are enabled).

  • #3446 - b64decode() now ignores ASCII whitespace in its input, so base64 text wrapped across lines by a MIME encoder or the base64 command-line tool decodes without the caller stripping it first. Only the six ASCII whitespace bytes are ignored; unlike Python's base64.b64decode(), any other byte outside the base64 alphabet still raises. The "length must be divisible by 4" error now counts only the significant characters.

  • #6834 - atol() (and therefore Int(String)) now raises for every value outside the Int range. Values just past Int.MAX (such as Int.MAX + 1) no longer wrap silently, and Int.MIN parses correctly by design rather than by wraparound. atol() now also raises instead of aborting on a string that holds only whitespace, or only whitespace and a sign.

OS and system

  • os.path.join() now inserts separators based on the accumulated path rather than the first argument, so join("/", "a", "b") returns /a/b (previously /ab) and join("a", "b/", "c") returns a/b/c (previously a/b//c).

  • #6838 - On macOS, os.stat() and os.lstat() no longer return a negative st_mode for regular files. Mojo declared the underlying mode_t and nlink_t C type aliases as signed 16-bit integers, but macOS defines them as unsigned, so any mode with the S_IFREG bit set (every regular file) sign-extended into a negative Int.

  • #6839 - On macOS, os.stat() and os.lstat() now report file timestamps with the correct nanosecond values. _CTimeSpec.as_nanoseconds() previously treated the timespec.tv_nsec field as microseconds, inflating the subsecond component by a factor of up to 1,000.

Other fixes

  • Counter.most_common(n) now returns all elements when n exceeds the number of unique elements, matching Python, instead of aborting.

  • #6833 - PythonObject no longer leaks a CPython reference per positional argument when calling a Python object, nor when setting an item, attribute, or set literal element.

Special thanks

Special thanks to our community contributors:

Amr Hesham (@AmrDeveloper), BlueDestination (@BlueDestination), Christoph Grüninger (@gruenich), Christoph Schlumpf (@christoph-schlumpf), Danilo Salve (@odanilosalve), David Dada (@obadafidii), Giorgos Smyridis (@gsmyridis), iMostfa (@iMostfa), Jay Hemnani (@jayhemnani9910), Kavindu Sachinthe (@kavix), Mahendra Rathore (@mahendrarathore1742), Manuel Saelices (@msaelices), Nithesh (@Nithesh8678), Nitin Krishna Mucheli (@NewtonChutney), Ratul (@ratulb), Sherlock Xu (@Sherlock113), Vihaan Agarwal (@VihaanAgarwal), Vladimir Babin (@chiliec), void (@robinber)