Skip to main content
Version: 1.0

v0.26.1 (2026-01-29)

✨ Highlights

  • Expanded reflection module. The reflection module now provides extensive compile-time introspection: struct field enumeration, types, and byte offsets; source location tracking; and trait conformance checking on dynamically obtained types. These APIs enable advanced metaprogramming patterns like automatic serialization and debug formatting. See Reflection and introspection.

  • Explicitly-destroyed types. Mojo now has first-class support for explicitly-destroyed types (sometimes referred to as "linear types"). The AnyType, Movable, and Copyable traits no longer require a __del__() method; use ImplicitlyDestructible when you need implicit destruction. Explicitly-destroyed types let you encode powerful invariants requiring explicit resource handling. See Language changes and Explicitly-destroyed types.

  • Typed errors. Functions can now specify what type they raise instead of defaulting to Error (for example, fn foo() raises CustomError -> Int). Typed errors are highly efficient—they compile to an alternate return value with no stack unwinding—making them suitable for GPU and embedded targets. See Language enhancements.

  • Traits with default implementations. The Hashable, Writable, and Equatable traits now provide default implementations that automatically derive behavior from struct fields using reflection. Simple structs can conform to these traits without writing any boilerplate—just ensure all fields conform to the same trait. See Traits with default implementations.

  • UInt type redesign. The UInt struct has been replaced by a type alias to Scalar[DType.uint], enabling more powerful generic programming over unsigned SIMD data types of machine word size. See Other library changes.

  • String UTF-8 safety. String now offers three explicit constructors for raw bytes: from_utf8= (validates and raises on error), from_utf8_lossy= (replaces invalid sequences with �), and unsafe_from_utf8= (no validation). This makes UTF-8 handling guarantees explicit at construction time. See String and text.

Documentation

  • The new Mojo quickstart page provides a "5-minute" introduction to installing Mojo and exploring basic language features.

  • The new Reflection page describes Mojo's new compile-time reflection capabilities, including example use cases.

  • Added a section to the Value destruction page about how to implement and use explicitly-destroyed types.

  • The new Jupyter Notebooks page provides step-by-step instructions for programming with Mojo in Google Colab and local JupyterLab environments, including GPU programming examples.

  • The new Materialization page describes how to make compile-time values available at run time.

Language enhancements

  • Mojo now supports raising "typed errors", where a function can specify what type it raises instead of defaulting to the Error type. This is done by specifying it after the raises keyword, for example, fn foo() raises CustomError -> Int.

    Raised errors in Mojo are very efficient - they work as an alternate return value: for example, a function like fn () raises Int -> Float32: compiles into code that returns either an Int or a Float32 and uses an implicit boolean result to determine which one is valid - there is no expensive stack unwinding or slow dynamic logic that is implied. This means that thrown errors work fine on GPUs and other embedded targets.

    The 'caught' type in a try block is automatically inferred to be the first thrown type inside of the try body, for example:

    try:
    print(foo())
    except err: # "err" is typed as CustomError
    print(err)

    Typed throws "just work" with generics, allowing the definition of higher order functions like:

    fn parametric_raise_example[ErrorType: AnyType](fp: fn () raises ErrorType) raises ErrorType:
    # ... presumably some iteration or other exciting stuff happening here.
    fp()

    This dovetails with other support to allow contextually generic thrown types, for example:

    fn call_parametric_raise_example[GenTy: AnyType](func_ptr: fn () raises GenTy):
    fn raise_int() raises Int: pass
    try:
    parametric_raise_example(raise_int)
    except err_int: # Typed as Int
    ref x: Int = err_int

    fn raise_string() raises String: pass
    try:
    parametric_raise_example(raise_string)
    except err_string: # Typed as String
    ref s: String = err_string

    try:
    parametric_raise_example(func_ptr)
    except err_gen: # Typed as GenTy
    ref s: GenTy = err_gen

    # Non-raising functions infer an error type of `Never`, allowing these
    # functions to propagate non-raisability across generic higher-order
    # functions conveniently.
    fn doesnt_raise(): pass
    # Note this isn't in a try block. Mojo knows 'parametric_raise_example'
    # doesn't raise because the 'doesnt_raise' function doesn't.
    parametric_raise_example(doesnt_raise)

    As part of this, context managers have been extended to support typed throws, and can also infer an error type if they need to handle it, for example:

    struct MyGenericExitCtxtMgr:
    # Called on entry to the with block.
    fn __enter__(self): ...
    # Called on exit from the with block when no error is thrown.
    fn __exit__(self): ...
    # Called on exit from the with block if an error is thrown.
    fn __exit__[ErrType: AnyType](self, err: ErrType) -> Bool: ...
  • Mojo now supports a Never type, which can never be instantiated. This type can be used for functions (like abort()) which do not have a normal return value, and for functions that are guaranteed to raise without returning a normal value. Functions that are declared to raise Never (and generic functions instantiated with Never as their error type) compile into the same ABI as functions that don't raise.

  • Mojo now allows the use of a comptime(x) expression to force a subexpression to be evaluated at compile time. This can help make working with certain types more elegant when you can't (or don't want to) materialize them into a runtime value. For example, if you just want the size from a compile time layout:

    fn takes_layout[a: Layout]():
    # materializes entire layout value just to get the size out of it
    print(a.size())
    # Could already work around this with a comptime declaration, verbosely.
    comptime a_size = a.size()
    print(a_size)
    # Can now tell Mojo to evaluate the expression at comptime.
    print(comptime(a.size()))
  • Mojo now differentiates between ... and pass in trait methods. The use of ... continues to denote no default implementation—pass now specifies a default do-nothing implementation. For example:

    trait T:
    # No default implementation
    fn foo(self): ...

    # Default implementation that does nothing
    fn bar(self) : pass

    The compiler will error on the use of pass to define a default implementation for a trait method with results:

    trait T:
    foo.mojo:2:26: error: trait method has results but default implementation returns no value; did you mean '...'?
    fn foo(self) -> Int: pass
    ^
    trait.mojo:2:8: note: in 'foo', declared here
    fn foo(self) -> Int: pass
    ^
  • Mojo now allows implicit conversions between function types from a non-raising function to a raising function. It also allows implicit conversions between function types whose result types are implicitly convertible.

    fn takes_raising_float(a: fn () raises -> Float32): ...
    fn returns_int() -> Int: ...
    fn example():
    # This is now ok.
    takes_raising_float(returns_int)
  • Mojo now allows functions that return references to convert to functions that return values if the type is implicitly copyable or implicitly convertible to the destination type:

    fn fn_returns_ref(x: SomeType) -> ref [x.field] Int: ...
    fn examples():
    # OK, Int result from fn_returns_ref can be implicitly copied.
    var f1 : fn (x: SomeType) -> Int = fn_returns_ref
    # OK, Int result from fn_returns_ref implicitly converts to Float64.
    var f2 : fn (x: SomeType) -> Float64 = fn_returns_ref
  • Context managers (used in with statements) can now define consuming exit methods—that is, fn __exit__(var self)—which can be useful for explicitly-destroyed context managers. This also works with deinit.

  • The deinit argument convention can now be applied to any argument of a struct method, but the argument type still must be of the enclosing struct type.

  • Mojo now supports the ... expression. It is a logically empty value of EllipsisType. It can be used in overloaded functions, for example, getitem() calls:

    struct YourType:
    fn __getitem__(self, idx: Int) -> Int:
    # ... behavior when passed x[i]
    fn __getitem__(self, idx: EllipsisType) -> Int:
    # ... behavior when passed x[...]

Language changes

  • The Mojo language basic trait hierarchy has changed to expand first-class support for explicitly-destroyed types (sometimes referred to as "linear types").

    The AnyType, Movable, and Copyable traits no longer require that a type provide a __del__() method that may be called by the compiler implicitly whenever an owned value is unused. Instead, the ImplicitlyDestructible trait should be used in generic code to require that a type is implicitly destructible.

    Explicitly-destroyed types enable Mojo programs to encode powerful invariants in the type system, by modeling a type in such a way that a user is required to take an action "in the future", rather than simply implicitly dropping an instance "on the floor".

    Code using T: AnyType can change to use T: ImplicitlyDestructible to preserve its pre-existing behavior following this change.

    Relatedly, the UnknownDestructibility trait is now no longer required, as it is equivalent to the new AnyType behavior.

  • Mojo no longer supports overloading functions on parameters alone: it will not try to disambiguate between fn foo[a: Int8](): and fn foo[a: Int32](): for example. Mojo never fully implemented the previous support in a reliable way, and removing this simplifies the language. It still supports overloading on function arguments of course.

  • The __next_ref__() method in for-each loops has been removed. Now you can implement the __next__() method of your iterator to return either a value or a reference. When directly using the collection, Mojo will use the ref-returning variant, but will allow it to conform to Iterator for use with generic algorithms (which use a copied value).

  • The origin_of(x) operator now returns a value of type Origin instead of an internal MLIR type, and aliases like ImmutOrigin are now Origin type as well.

  • The Origin.cast_from[x] syntax has been replaced with a safe implicit conversion from any origin to an immutable origin (ImmutOrigin(x)) and an explicit unsafe conversion (unsafe_origin_mutcast[origin, mut=m]).

  • The *_ and **_ syntax for explicitly unpacked parameters has been replaced with a simplified ... syntax. Instead of T[4, 5, *_, **_] you can now use T[4, 5, ...]. The ... delays binding of both keyword and non-keyword parameters.

  • The compiler will now warn on the use of alias keyword and suggest comptime instead.

  • The compiler will now warn on unqualified access to struct parameters, for example

    @fieldwise_init
    struct MyStuff[my_param: Int]:
    fn give_me_stuff(self) -> Int:
    # Warning: unqualified access to struct parameter 'my_param'; use 'Self.my_param' instead
    return my_param
  • The Mojo compiler generates more clear error messages when diagnosing invalid calls: it mentions the argument name, instead of "argument #4".

Library changes

Reflection and introspection

  • The reflection module has been significantly expanded with new compile-time introspection capabilities. The module has moved from compile.reflection to a top-level reflection module (update imports from from compile.reflection import ... to from reflection import ...). Internally, the module is now organized into type_info and struct_fields submodules, though the public API via from reflection import ... remains unchanged.

    Struct field introspection - New APIs for compile-time struct analysis:

    • struct_field_count[T]() - Returns the number of fields in a struct
    • struct_field_names[T]() - Returns field names as InlineArray [StaticString, N]
    • struct_field_types[T]() - Returns a variadic of all field types
    • struct_field_index_by_name[T, name]() - Returns field index by name
    • struct_field_type_by_name[T, name]() - Returns field type wrapped in ReflectedType

    These APIs work with both concrete types and generic type parameters:

    fn print_fields[T: AnyType]():
    comptime names = struct_field_names[T]()
    comptime types = struct_field_types[T]()
    @parameter
    for i in range(struct_field_count[T]()):
    print(names[i], get_type_name[types[i]]())

    Field access by index - Two new magic functions enable index-based field access without copying:

    • __struct_field_type_at_index(T, idx) - Returns field type at index
    • __struct_field_ref(idx, ref s) - Returns a reference to the field

    Unlike kgen.struct.extract which copies, __struct_field_ref() returns a reference, enabling reflection utilities to work with non-copyable types:

    fn print_all_fields[T: AnyType](ref s: T):
    comptime names = struct_field_names[T]()
    @parameter
    for i in range(struct_field_count[T]()):
    print(names[i], "=", __struct_field_ref(i, s))

    Field byte offsets - offset_of[T, name=field_name]() returns the byte offset of a named field within a struct, enabling no-copy serialization and other low-level memory operations. The offset is computed at compile time using the target's data layout, correctly accounting for alignment padding. This is analogous to C/C++'s offsetof and Rust's offset_of! macro. An offset_of[T, index=i]() overload is also available to look up by field index.

    from reflection import offset_of

    struct Point:
    var x: Int # offset 0
    var y: Float64 # offset 8 (aligned)

    fn main():
    comptime x_off = offset_of[Point, name="x"]() # 0
    comptime y_off = offset_of[Point, name="y"]() # 8

    Type introspection utilities:

    • is_struct_type[T]() - Returns True if T is a Mojo struct type. Useful for guarding reflection code that uses struct-specific APIs to avoid compiler errors on non-struct types (for example, MLIR primitive types). Use @parameter if since these APIs are evaluated at compile time. (Issue #5734)

    • get_base_type_name[T]() - Returns the unqualified name of a parameterized type's base type. For example, get_base_type_name[List[Int]]() returns "List". Useful for identifying collection types regardless of element types. (Issue #5735)

    Source location introspection:

    • SourceLocation - A struct holding filename, line, and column information
    • source_location() - Returns the location where it's called
    • call_location() - Returns the location where the caller was invoked (requires the caller to be @always_inline)

    These were previously internal APIs (_SourceLocation, __source_location, __call_location) in builtin._location. The old module has been removed.

    from reflection import source_location, call_location, SourceLocation

    fn main():
    var loc = source_location()
    print(loc) # main.mojo:5:15

    @always_inline
    fn log_here():
    var caller_loc = call_location()
    print("Called from:", caller_loc)

    Note: These APIs do not work correctly in parameter expressions (comptime contexts return placeholder values).

    Trait conformance checking - The conforms_to() builtin now accepts types from reflection APIs like struct_field_types[T](), enabling conformance checks on dynamically obtained field types:

    @parameter
    for i in range(struct_field_count[MyStruct]()):
    comptime field_type = struct_field_types[MyStruct]()[i]
    @parameter
    if conforms_to(field_type, Copyable):
    print("Field", i, "is Copyable")

Traits with default implementations

  • Several traits now have default implementations that use reflection to automatically derive behavior from struct fields. This means simple structs can conform to these traits without implementing any methods - all fields just need to conform to the same trait:

    Hashable - Default __hash__() hashes all fields:

    @fieldwise_init
    struct Point(Hashable):
    var x: Float64
    var y: Float64

    hash(Point(1.5, 2.7)) # Works automatically

    Writable - Default write_to() formats all fields:

    @fieldwise_init
    struct Point(Writable):
    var x: Float64
    var y: Float64

    print(Point(1.5, 2.7)) # Point(x=1.5, y=2.7)

    Equatable - Default __eq__() compares all fields:

    @fieldwise_init
    struct Point(Equatable):
    var x: Int
    var y: Int

    print(Point(1, 2) == Point(1, 2)) # True

    Note: The default Equatable performs memberwise equality, which may not be appropriate for types with floating-point fields (due to NaN semantics). Override any of these methods for custom behavior.

Collections and iterators

  • Removed List variadic initializer.

    • Statements like:

      var x = List[Int32](1, 2, 3)

      can be updated to:

      var x: List[Int32] = [1, 2, 3]
    • Expressions like:

      var x = foo(List[Float32](1, 2, 3))

      can be updated to move the explicit type "hint" around the first element:

      var x = foo([Float32(1), 2, 3])
    • Expressions like:

      var data = Span(List[Byte](1, 2, 3))

      can be updated to move the explicit element type to the Span:

      var data = Span[Byte]([1, 2, 3])
  • List slicing without a stride now returns a Span, instead of a List and no longer allocates memory.

  • InlineArray no longer conforms to ImplicitlyCopyable. Users must explicitly copy arrays or take references.

  • IndexList is no longer implicitly constructible from Int. Previously, the fill constructor (which broadcasts a single Int to all elements) was marked @implicit, allowing code like var x: IndexList[3] = 5 which would create (5, 5, 5). This implicit conversion has been removed to improve type safety. Use explicit construction instead: IndexList[3](5).

  • Dict now raises a custom DictKeyError type on failure, making lookup failures more efficient to handle.

    var d = Dict[String, Int]()
    var key = "missing_key"
    try:
    _ = d[key]
    except e:
    print(e) # Prints: DictKeyError
  • New ContiguousSlice and StridedSlice types were added to the builtin_slice module to support specialization for slicing without strides.

  • Span now conforms to Iterable.

  • any() and all() now work over Iterables, which means they can act over the result of map().

  • Tuples have been improved:

    • Tuples can now be concatenated with Tuple.concat().
    • Tuples can now be reversed with Tuple.reverse().
  • The peekable() function has been added to iter. This allows users to peek at the next element of an iterator without advancing it.

String and text

  • String has had its UTF-8 guarantees strengthened.

    • It now has three separate constructors when converting raw bytes (Span[Byte]) to a String:
      • String(from_utf8=...): Raises an error if the bytes are invalid UTF-8
      • String(from_utf8_lossy=...): Converts invalid UTF-8 byte sequences into the (U+FFFD, �) replacement character and does not raise an error.
      • String(unsafe_from_utf8=...): Unsafely assumes the input bytes are valid UTF-8 without any checks.
    • append_byte() has been deprecated and has been replaced with append().
  • The strip(), lstrip(), and rstrip() methods of String and StringSlice now support stripping multi-byte unicode codepoints. Additionally lstrip() and strip() will no longer produce invalid UTF-8 if the "chars" string contains characters sharing their first byte with a character in the string to be stripped.

  • StringLiteral.format() now emits a compile-time constraint error if the format string is invalid (instead of a runtime error).

    "Hello, {!invalid}".format("world")
    # note: constraint failed: Conversion flag "invalid" not recognized.
  • StringSlice.char_length() has been renamed count_codepoints(). The same function was added to String and StringLiteral.

  • Added a CStringSlice as a type-safe way to interact with nul-terminated c-style strings (const char*).

  • Removed String.join(*Writable) overload that takes a variadic sequence of arguments, as it could be ambiguous with the remaining String.join(Span[Writable]) overload.

  • Removed the Int.__init__(self, value: StringSlice, base: UInt) constructor. Users should call atol() directly.

Pointer and memory

  • OwnedDLHandle.get_symbol() now returns UnsafePointer [T, MutAnyOrigin] instead of UnsafePointer[T, ImmutAnyOrigin]. The vast majority of symbols loaded from shared libraries are meant to be used mutably, and it's safer to go from mutable → immutable (via .as_immutable()) than from immutable → mutable (via .unsafe_mut_cast[True]()). Users who need immutable pointers can now simply call .as_immutable() on the result.

  • The "LegacyUnsafePointer" type has been changed to take its mutability as a first inferred parameter without a default, rather than a later explicit parameter with a default value of true. We recommend moving off of this type as soon as possible, but to roughly emulate the prior behavior, try out:

    from memory import LegacyUnsafePointer
    comptime UnsafePointer = LegacyUnsafePointer[mut=True, *_, **_]
  • External origins are now expressed using type level {Mut,Immut,}ExternalOrigin aliases instead of being spelled like Origin[True].external, improving consistency with other origin types.

  • UnsafePointer can now be initialized from a raw memory address using the unsafe_from_address initializer.

  • alloc() now has a debug_assert() ensuring count is non-negative.

Explicitly-destroyed types

  • Basic support for explicitly-destroyed types in the standard library is now available. Explicitly-destroyed types—sometimes referred to as "linear types"—are types that don't define a __del__() method that the compiler can call automatically to destroy an instance. Instead, a explicitly-destroyed type must provide a named method taking deinit self that the programmer is required to call explicitly whenever an owned instance is no longer used.

    The updated AnyType trait can be used in parameters to denote generic code that supports object instances that can't be implicitly destroyed.

    • Span, UnsafePointer, Pointer, and OwnedPointer can point to explicitly-destroyed types.
      • Added UnsafePointer.destroy_pointee_with(), for destroying explicitly-destroyed types in-place using a function pointer to the type's destructor.
    • List, InlineArray, Optional, Variant, VariadicListMem, and VariadicPack can now contain explicitly-destroyed types.
      • Variant.take() now takes deinit self instead of mut self.
      • Added Variant.destroy_with() for destroying an explicitly-destroyed type in-place by passing in the type's destructor function.
      • The *args language syntax for arguments now supports explicitly-destroyed types.
    • Iterator.Element no longer requires ImplicitlyDestructible.
    • UnsafeMaybeUninitialized can now contain explicitly-destroyed types.

Type system and traits

  • Using a new 'unconditional conformances' technique leveraging conforms_to() and trait_downcast() to perform "late" element type conformance checking, some standard library types are now able to conform to traits that they could not previously:

    • List, Dict, Set, Deque, InlineArray, and LinkedList now conform to Writable, Stringable, and Representable. List also conforms to Equatable.
    • Pointer, ArcPointer, and OwnedPointer now conform to Writable.
    • Iterator, Tuple, Variant, and Optional no longer require their element types to be Copyable.
  • The Iterator trait and for-each loop have removed the __has_next__() method and now use a __next__() method that raises StopIteration. This follows Python precedent better, is more convenient to implement, and can be a minor performance win in some cases.

  • The ImplicitlyBoolable trait has been removed. This trait enabled types to implicitly convert to Bool. This behavior was rarely used, and could lead to subtle bugs, for example mistakenly passing types like Int or UnsafePointer to an argument expecting a Bool would silently compile successfully.

  • The Error type no longer conforms to Boolable or Defaultable. Errors must now be constructed with meaningful context, and optionality should be expressed through Optional[Error] rather than treating errors as boolean values.

  • The Copyable trait now refines the Movable trait. This means that structs and generic algorithms that already require Copyable don't need to also mention they require Movable.

  • The EqualityComparable trait has been deprecated in favor of Equatable, which has identical functionality.

  • We have removed Identifiable from enum-like types (such as DType and AddressSpace). This change is related to the idea that Identifiable is for comparing memory addresses.

Python interoperability

  • The ConvertibleFromPython trait and associated initializers now have a required keyword argument. Before: Int(pyObj). After: Int(py=pyObj). This avoids ambiguities in cases where either multiple overloads could apply, or where implicit conversions to PythonObject could mask that a Python operation was happening.

  • PythonObject now supports implicit conversion from None, allowing more natural Python-like code:

    var obj: PythonObject = None  # Now works without explicit PythonObject(None)

    fn returns_none() -> PythonObject:
    return None # Implicit conversion

File I/O and OS

  • Basic file I/O operations in the io module are now implemented natively in Mojo using direct libc system calls (open(), close(), read(), write(), lseek()). The FileHandle type no longer depends on CompilerRT functions, providing better performance and transparency. Error handling now includes errno-based messages for improved diagnostics.

  • The os.process submodule has been added with utilities to spawn and wait on processes. These use posix_spawn() and do not go through the system shell.

  • Various OS wrapper functions now include the value of errno in the raised error message.

  • The os module now exposes a link() function, wrapping the unix link(2) system call.

  • The os module now exposes a symlink() function, wrapping the unix symlink(2) syscall.

GPU programming

  • Layout no longer conforms to ImplicitlyCopyable. The motivation for this change is that it was easy to accidentally materialize a Layout at runtime using methods such as Layout.size(). There are a few downstream changes users will have to adapt to with this change. Below are some common patterns which will help with this transition:

    from layout import Layout


    fn foo(a: Layout) -> Layout:
    return a.copy() # `a` needs to be explicitly copied.


    fn bar(var a: Layout) -> Layout:
    return a^ # If a is taken by value, one can use the transfer operator.


    fn baz():
    comptime a = Layout.row_major(4, 4)
    # `a` needs to be materialized because `foo` returns a type that
    # is not ImplicitlyCopyable (`Layout`).
    var b = foo(materialize[a]())

    # `bar` moves `b` by value, but `foo` also takes `b` by reference,
    # so we need to explicitly copy.
    _ = bar(b.copy())
    _ = foo(b)
    # Since we are no longer using `b`, it's fine to take it by value here.
    _ = bar(b^)

    # Since `Layout.size()` returns an `Int`, we can use a `comptime`
    # expression to compute the return value without materializing `a`.
    for i in range(comptime (a.size())):
    ...
  • DeviceContext.enqueue_function_checked() and DeviceStream.enqueue_function_checked() have been renamed to enqueue_function(). Similarly, DeviceContext.compile_function_checked() has been renamed to compile_function().

  • DeviceContext.enqueue_function() and DeviceContext.enqueue_function_experimental() now automatically infer func_attribute to FuncAttribute.MAX_DYNAMIC_SHARED_SIZE_BYTES(shared_mem_bytes) when shared_mem_bytes is specified but func_attribute is not, for NVIDIA GPUs with allocations > 48KB. This eliminates the need to specify the same shared memory size twice in many cases, reducing boilerplate and preventing mismatched values. On AMD GPUs or for allocations ≤ 48KB, explicit func_attribute values should be provided when needed.

  • The inlined_assembly() function is now publicly exported from the sys module, allowing users to embed raw assembly instructions directly into Mojo code. This provides fine-grained control over hardware operations using LLVM-style inline assembly syntax. Example:

    from sys import inlined_assembly

    # Convert bfloat16 to float32 on NVIDIA GPU using PTX assembly.
    var result = inlined_assembly[
    "cvt.f32.bf16 $0, $1;",
    Float32,
    constraints="=f,h",
    has_side_effect=False,
    ](my_bf16_as_int16)

Formatting and output

  • Writer has been reworked to support only UTF-8 data instead of arbitrary Byte sequences. The write_bytes() method has been replaced with write_string().

    • In line with these changes, String's write_bytes() method has also been deprecated, and its initializer __init__(out self, *, bytes: Span[Byte]) has had its keyword argument renamed to unsafe_from_utf8. This brings it more in line with the existing StringSlice constructors and explicitly states that construction from arbitrary bytes is inherently unsafe.
  • Writer and Writable have been moved into a new format module and out of io. These traits are not directly related to binary i/o, but are rather closely tied to type/value string formatting.

  • The Writable trait now supports debug formatting through an optional write_repr_to() method, called by repr() and the {!r} format specifier. Additionally, repr() and string formatting methods (.format() on String, StringSlice, and StringLiteral) now accept Writable types, enabling efficient formatting without intermediate string allocations. To preserve existing behavior, types implementing both Stringable & Representable and Writable will continue using Stringable & Representable methods; only types implementing Writable alone will use the new code paths.

  • Counter now conforms to Writable, Stringable, and Representable.

  • black_box() has been added to the benchmark utilities as a way to prevent the compiler from aggressively optimizing out values. Similar to keep(), however, it returns its argument.

Other library changes

  • The UInt struct has been replaced by a new UInt type alias to Scalar[DType.uint]. This is a major change that enables more powerful generic programming by abstracting over unsigned SIMD data dtypes of machine word size.

    This change will likely break code that relies on implicit conversions to/from UInt and Int/SIMD. The SIMD type is also slightly less foldable at compile time, which can cause some code in where clauses, comptime expressions, and other code in parametric contexts to now fail or crash. These shortcomings will be addressed in subsequent patches as needed.

  • Implicit conversion between Int and UInt has been removed.

  • The random module now uses a pure Mojo implementation based on the Philox algorithm (via an internal wrapper), replacing the previous CompilerRT C++ dependency. The Philox algorithm provides excellent statistical quality, works on both CPU and GPU, and makes random number generation fully transparent and source-available. Note that this changes the random number sequence for a given seed value, which may affect tests or code relying on reproducible sequences.

  • StringableRaising has been deprecated and its usages in the stdlib have been removed.

  • Variadic now has zip_types(), zip_values(), and slice_types().

Tooling changes

  • The Mojo compiler now supports the -Werror flag, which treats all warnings as compilation errors. This is useful for enforcing stricter code quality standards, particularly in CI/CD pipelines. The flag works with the Mojo compiler tools (mojo run, mojo build, mojo package, mojo doc). When used with --disable-warnings, warnings are promoted to errors first, so the errors are not suppressed.

    • The counterpart -Wno-error flag disables treating warnings as errors. When both flags are specified, the last one wins.
  • Specifying CUDA architectures with --target-accelerator now expects a sm version string rather than just a compute capability. For example, --target-accelerator=nvidia:80 should be changed to --target-accelerator=nvidia:sm_80. If an incorrect format is used for the version, the compiler will default to the lowest supported sm version.

  • The Mojo LSP server now debounces document updates to reduce CPU usage during rapid typing. Previously, every keystroke triggered a full document parse; now updates are coalesced with a 150ms delay, reducing parse frequency by 10-50x during active editing.

  • The Mojo compiler now "diffs" very long types in error messages to explain what is going on in an easier to understand way.

  • Elaboration error printing with different levels of verbosity offers control on how parameter values are displayed as part of elaboration errors when function instantiation fails. --elaboration-error-verbose=value now takes a value, where:

    • no-params means don't display any concrete parameter values. This is helpful to collapse recursion-related error messages into shorter blobs.
    • simple-params means display concretized parameter values for simple types, including numeric types and strings, in a user-friendly format (default value).
    • all-params means show all concrete parameter values. This is for advanced programmers who don't mind reading MLIR attributes but want more visibility of parameter values.
  • --elaboration-max-depth is added to control maximum elaborator instantiation depth. This (unsigned) value helps to detect compile time recursion. The default is std::numeric_limits<unsigned>::max().

  • Docstring validation with --validate-doc-strings now emits an error when an fn function is declared to raise an error (raises) but it's missing a Raises docstring (previously it emitted only a warning). Because Mojo automatically treats all def functions as raising functions, we don't enforce Raises docs for def functions (to avoid noisy false positives).

  • Docstring validation now includes comptime aliases. The --diagnose-missing-doc-strings flag now checks that public aliases have properly formatted docstrings (summary ends with period, starts with capital letter). Parametric aliases are also checked for proper Parameters: sections.

  • The --validate-doc-strings flag has been deprecated for mojo doc and removed from other tools (mojo build, mojo run, mojo package). Use -Werror instead to treat warnings as errors.

  • The Mojo compiler now supports the -Xlinker flag to pass options on directly to the linker, for example:

    mojo build -Xlinker -lfoo main.mojo

    Note: this option only has an effect with mojo build. With mojo run, the arguments are ignored and a warning is issued.

  • The Mojo compiler now supports the --experimental-export-fixit flag for mojo build, mojo run, and mojo package. This flag exports fix-its to a YAML file compatible with clang-apply-replacements, instead of applying them directly. This is useful for integrating Mojo's fix-it suggestions into external tooling workflows. The flag is mutually exclusive with --experimental-fixit (which applies fix-its directly).

  • The Mojo Debugger mojo break-on-raise feature now works correctly with multiple targets in a debugger instance. The setting is per-target.

Experimental changes

Changes described in this section are experimental and may be changed, replaced, or removed in future releases.

  • Mojo now supports compile-time trait conformance check (via conforms_to()) and downcast (via trait_downcast()). This allows users to implement features like static dispatching based on trait conformance. For example:

    fn maybe_print[T : AnyType](maybe_printable : T):
    @parameter
    if conforms_to(T, Writable):
    print(trait_downcast[Writable](maybe_printable))
    else:
    print("[UNPRINTABLE]")
  • Added support for DType expressions in where clauses:

    fn foo[dt: DType]() -> Int where dt == DType.int32:
    return 42

    Currently, the following expressions are supported:

    • equality and inequality
    • is_signed(), is_unsigned(), is_numeric(), is_integral(), is_floating_point(), is_float8(), is_half_float()
  • Added support for SIMD expressions in where clauses:

    fn foo[dt: DType, x: Int]() -> Int where SIMD[dt, 4](x) + 2 > SIMD[dt, 4](0):
    return 42

    Currently, the following expressions are supported:

    • default construction and construction from Int and IntLiteral
    • equality, inequality, and other comparison operators
    • addition, subtraction, and multiplication
    • bitwise logical operations, excluding shifts

Removed

  • The following deprecated GPU compatibility modules have been removed:

    • gpu.id - Use from gpu import block_idx, thread_idx, ... instead
    • gpu.block - Use from gpu.primitives.block import ... instead
    • gpu.warp - Use from gpu.primitives.warp import ... instead
    • gpu.cluster - Use from gpu.primitives.cluster import ... instead
    • gpu.grid_controls - Use from gpu.primitives.grid_controls import ... instead
    • gpu.mma - Use from gpu.compute.mma import ... instead
    • gpu.mma_operand_descriptor - Use from gpu.compute.mma_operand_descriptor import ... instead
    • gpu.mma_util - Use from gpu.compute.mma_util import ... instead
    • gpu.mma_sm100 - Use from gpu.compute.arch.mma_nvidia_sm100 import ... instead
    • gpu.semaphore - Use from gpu.sync.semaphore import ... instead
    • gpu.tcgen05 - Use from gpu.compute.arch.tcgen05 import ... instead
  • The DeviceContext enqueue_function_unchecked() and compile_function_unchecked() have been removed. Please migrate the code to use enqueue_function() and compile_function().

  • NDBuffer has been removed. Use LayoutTensor instead.

  • The UnsafePointer.offset() method is now deprecated. Use pointer arithmetic instead:

    # Before
    new_ptr = ptr.offset(n)

    # After
    new_ptr = ptr + n

Fixed

  • Several reflection-related compiler crashes have been fixed:
    • Issue #5731: Reflection functions now work correctly on builtin types like Int, NoneType, and Origin.
    • Issue #5732: get_type_name() now handles types with constructor calls in their parameters (like A[B(True)]) when extracted via struct_field_types().
    • Issue #5723: get_type_name() now handles nested parametric types from struct_field_types().
    • Issue #5754: struct_field_type_by_name() now works correctly when using ReflectedType.T as a type annotation.
    • Issue #5808: rebind() and rebind_var() now accept downcasted types from struct_field_types(), allowing patterns like rebind_var[types[i]](downcast[types[i], Trait]()^).
  • Issue #5618: Compiler crash when should be implicit conversion error.
  • Issue #5361: mojo doc crashes on alias of parametrized function with origin.
  • Issue #5137: Tail call optimization doesn't happen for tail recursive functions with raises.
  • Issue #5138: Tail call optimization doesn't happen for functions with local stack temporaries.
  • Mojo no longer complains about "cannot infer parameter X" when unrelated type checking errors happen in complex parametric code. It now gives much more useful and actionable error messages in these cases.
  • time.sleep() now works correctly for durations longer than 1 millisecond on NVIDIA GPUs. Previously, sleep durations were silently capped at 1ms due to a hardware limitation in the underlying nanosleep intrinsic. AMD GPUs now have basic sleep support using the s_sleep instruction, which is sufficient for spin-wait backoff operations though it doesn't provide accurate wall-clock timing. Additionally, global_perf_counter_ns() is now exported from the time package for GPU code that needs nanosecond-resolution timing.
  • Issue #5578: ownership overloading not working when used with ref.
  • Issue #1850: Mojo assumes string literal at start of a function is a doc comment
  • Issue #4501: Incorrect parsing of incomplete assignment
  • Issue #4765: Parser accepts pointless var ref a = n binding form
  • Codepoint.unsafe_decode_utf8_codepoint() no longer returns Codepoint(0) (NUL) when passed an empty span. Instead, a debug_assert() now enforces the requirement that the input span be non-empty, consistent with the function's existing safety contract.
  • Issue #5635: Deque shrink reallocation incorrectly handled empty deque with capacity > min_capacity.

Special thanks

Special thanks to our community contributors:

Alex Maldonado (@aalexmmaldonado), Bernhard Merkle (@bmerkle), Brian Grenier (@bgreni), Christoph Schlumpf (@christoph-schlumpf), Duba Sirisha (@SirishaDuba), Ethan Wu (@YichengDWu), Gunasekar (@sdgunaa), Hristo (Izo) G. (@izo0x90), Johannes Laute (@jaidmin), Jordan Rinder (@jrinder42), josiahls (@josiahls), Krish Gupta (@KrxGu), Magi Sharma J (@magi8101), Manuel Saelices (@msaelices), martinvuyk (@martinvuyk), Richard Johnsson (@jmikaelr), Ritesh Goru (@BlackWingedKing), Ross Campbell (@RossCampbellDev), RWayne93 (@RWayne93), soraros (@soraros), Sören Brunk (@sbrunk), turakz (@turakz), Valentin Erokhin (@saviorand), YeonguChoe (@YeonguChoe)