Skip to main content
Log in

Mojo🔥 changelog

This is a list of changes to the Mojo language, standard library, and tools.

To check your current version, run mojo --version. To update the version of Mojo for your project with the magic package manager, follow the instructions in Update a package to update the max package.

v24.6 (2024-12-17)

✨ Highlights

Here's a brief summary of some of the major changes in this release, with more detailed information in the following sections:

  • The inout and borrowed argument conventions have been renamed to mut and read, respectively. A new out convention has been added for the self argument in constructors and for named results. See Language changes for details.

  • Lifetime and related types in the standard library have been renamed to Origin to better clarify that parameters of this type indicate where a reference is derived from, not the more complicated notion of where a variable is initialized and destroyed. As a consequence the __lifetime_of() operator is now named __origin_of().

    There are also a number of other origin-related improvements in this release, including being able to specify a union of origins by listing multiple values in the __origin_of() operator or inside the ref origin specifier (ref [a, b]). For details, see Language changes.

    For background information and rationale on the name change see the proposal. For more information on origins, see Lifetimes, origins and references in the Mojo Manual.

  • Implicit conversions are now opt-in using the @implicit decorator. See Language changes for details.

  • The standard library has added several new types, including Deque (a double-ended queue) and OwnedPointer (safe, single-owner, non-nullable smart pointer). See Standard library changes for details.

  • The VS Code extension now supports setting data breakpoints and function breakpoints, and the Mojo LLDB debugger supports symbol breakpoints, such as b main or b my_module::main.

  • We've made a number of improvement to how information is displayed in error messages, LSP, and generated API documentation. For details, see Tooling changes.

  • And we've added a number of new docs, including a brand new Mojo tutorial, new pages on operators and expressions, error handling, and pointers, and many smaller additions and improvements.

Language changes

  • Argument convention changes:

    • The inout and borrowed argument conventions have been renamed to mut (for "mutate") and read, respectively. These verbs reflect what the callee can do to the argument value passed in by the caller, without requiring the programmer to know about advanced features like references.

      For information on Mojo's argument conventions, see Argument conventions in the Mojo Manual.

    • The argument convention for the self argument in the __init__(), __copyinit__(), and __moveinit__() methods has been changed from inout to out, reflecting that a constructor method initializes its self value without reading from it. This also enables spelling the type of an initializer correctly, which was not supported before:

      struct Foo:
      fn __init__(out self): pass

      fn test():
      # This works now
      var fnPtr : fn(out x: Foo)->None = Foo.__init__

      var someFoo : Foo
      fnPtr(someFoo) # initializes someFoo.
      struct Foo:
      fn __init__(out self): pass

      fn test():
      # This works now
      var fnPtr : fn(out x: Foo)->None = Foo.__init__

      var someFoo : Foo
      fnPtr(someFoo) # initializes someFoo.

      The previous fn __init__(inout self) syntax is still supported in this release of Mojo, but will be removed in the future. Please migrate to the new syntax.

    • Similarly, the spelling of named results has switched to use out syntax instead of -> T as name. Functions may have at most one named result or return type specified with the usual -> syntax. out arguments may occur anywhere in the argument list, but are typically last (except for __init__ methods, where they are typically first).

      # This function has type "fn() -> String"
      fn example(out result: String):
      result = "foo"
      # This function has type "fn() -> String"
      fn example(out result: String):
      result = "foo"

      The parser still accepts the old syntax as a synonym for this, but that will eventually be deprecated and removed.

      This was discussed extensively in a public proposal. For more information, see Named results in the Mojo Manual.

  • Single argument constructors now require the @implicit decorator to allow for implicit conversions. Previously you could define an __init__ that takes a single argument:

    struct Foo:
    var value: Int

    fn __init__(out self, value: Int):
    self.value = value
    struct Foo:
    var value: Int

    fn __init__(out self, value: Int):
    self.value = value

    And this would allow you to pass an Int in the position of a Foo:

    fn func(foo: Foo):
    print("implicitly converted Int to Foo:", foo.value)

    fn main():
    func(Int(42))
    fn func(foo: Foo):
    print("implicitly converted Int to Foo:", foo.value)

    fn main():
    func(Int(42))

    This can result in complicated errors that are difficult to debug. By default this implicit behavior is now turned off, so you have to explicitly construct Foo:

    fn main():
    func(Foo(42))
    fn main():
    func(Foo(42))

    You can still opt into implicit conversions by adding the @implicit decorator. For example, to enable implicit conversions from Int to Foo:

    struct Foo:
    var value: Int

    @implicit
    fn __init__(out self, value: Int):
    self.value = value
    struct Foo:
    var value: Int

    @implicit
    fn __init__(out self, value: Int):
    self.value = value

    For more information see Constructors and implicit conversion in the Mojo Manual.

  • Origin-related changes:

    • The AnyLifetime type (useful for declaring origin types as parameters) has has been renamed to Origin and the __lifetime_of() operator renamed to __origin_of().

    • Origin is now a complete wrapper around the MLIR origin type.

      • The Origin.type alias has been renamed to _mlir_origin. In parameter lists, you can now write just Origin[..], instead of Origin[..].type.

      • ImmutableOrigin and MutableOrigin are now, respectively, just aliases for Origin[False] and Origin[True].

      • Origin struct values are now supported in the origin specifier of a ref [..] argument.

      • Added Origin.cast_from for casting the mutability of an origin value.

    • ref arguments and results now allow for providing a memory value directly in the origin specifier, rather than requiring the use of __origin_of(). It is still fine to use __origin_of() explicitly though, and this is required when specifying origins for parameters (e.g. to the Pointer type). For example, this is now valid without __origin_of():

      fn return_ref(a: String) -> ref [a] String:
      return a
      fn return_ref(a: String) -> ref [a] String:
      return a
    • Various improvements to origin handling and syntax have landed, including support for the ternary operator and allowing multiple arguments in a ref specifier (which are implicitly unions). This enables expression of simple algorithms cleanly:

      fn my_min[T: Comparable](ref a: T, ref b: T) -> ref [a, b] T:
      return a if a < b else b
      fn my_min[T: Comparable](ref a: T, ref b: T) -> ref [a, b] T:
      return a if a < b else b

      It is also nice that my_min automatically and implicitly propagates the mutability of its arguments, so things like my_min(str1, str2) += "foo" is valid.

    • ref function arguments without an origin clause are now treated as ref [_], which is more syntactically convenient and consistent:

      fn takes_and_return_ref(ref a: String) -> ref [a] String:
      return a
      fn takes_and_return_ref(ref a: String) -> ref [a] String:
      return a
    • The __type_of(x) and __origin_of(x) operators are much more general now: they allow arbitrary expressions inside of them, allow referring to dynamic values in parameter contexts, and even allow referring to raising functions in non-raising contexts. These operations never evaluate their expression, so any side effects that occur in the expression are never evaluated at runtime, eliminating concerns about __type_of(expensive()) being a problem.

    • The destructor insertion logic in Mojo is now aware that types that take an MutableAnyOrigin or ImmutableAnyOrigin as part of their signature could potentially access any live value that destructor insertion is tracking, eliminating a significant usability issue with unsafe APIs like UnsafePointer. Consider a typical example working with strings before this change:

      var str = String(...)
      var ptr = str.unsafe_ptr()
      some_low_level_api(ptr)
      _ = str^ # OLD HACK: Explicitly keep string alive until here!
      var str = String(...)
      var ptr = str.unsafe_ptr()
      some_low_level_api(ptr)
      _ = str^ # OLD HACK: Explicitly keep string alive until here!

      The _ = str^ pattern was formerly required because the Mojo compiler has no idea what "ptr" might reference. As a consequence, it had no idea that some_low_level_api() might access str and therefore thought it was ok to destroy the String before the call - this is why the explicit lifetime extension was required.

      Mojo now knows that UnsafePointer may access the MutableAnyOrigin origin, and now assumes that any API that uses that origin could use live values. In this case, it assumes that some_low_level_api() might access str and because it might be using it, it cannot destroy str until after the call. The consequence of this is that the old hack is no longer needed for these cases!

    • Function types now accept an origin set parameter. This parameter represents the origins of values captured by a parameter closure. The compiler automatically tags parameter closures with the right set of origins. This enables lifetimes and parameter closures to correctly compose.

      fn call_it[f: fn() capturing [_] -> None]():
      f()

      fn test():
      var msg = String("hello world")

      @parameter
      fn say_hi():
      print(msg)

      call_it[say_hi]()
      # no longer need to write `_ = msg^`!!
      fn call_it[f: fn() capturing [_] -> None]():
      f()

      fn test():
      var msg = String("hello world")

      @parameter
      fn say_hi():
      print(msg)

      call_it[say_hi]()
      # no longer need to write `_ = msg^`!!

      Note that this only works for higher-order functions which have explicitly added [_] as the capture origins. By default, the compiler still assumes a capturing closure does not reference any origins. This will soon change.

  • Infer-only parameters may now be explicitly bound with keywords, enabling some important patterns in the standard library:

    struct StringSlice[is_mutable: Bool, //, origin: Origin[is_mutable]]: ...
    alias ImmStringSlice = StringSlice[is_mutable=False]
    # This auto-parameterizes on the origin, but constrains it to being an
    # immutable slice instead of a potentially mutable one.
    fn take_imm_slice(a: ImmStringSlice): ...
    struct StringSlice[is_mutable: Bool, //, origin: Origin[is_mutable]]: ...
    alias ImmStringSlice = StringSlice[is_mutable=False]
    # This auto-parameterizes on the origin, but constrains it to being an
    # immutable slice instead of a potentially mutable one.
    fn take_imm_slice(a: ImmStringSlice): ...
  • The flag for turning on asserts has changed, e.g. to enable all checks:

    mojo -D ASSERT=all main.mojo
    mojo -D ASSERT=all main.mojo

    The levels are:

    • none: all assertions off
    • warn: print assertion errors e.g. for multithreaded tests (previously -D ASSERT_WARNING)
    • safe: the default mode for standard CPU safety assertions
    • all: turn on all assertions (previously -D MOJO_ENABLE_ASSERTIONS)

    You can now also pass Stringable args to format a message, which will have no runtime penalty or IR bloat cost when assertions are off. Previously you had to:

    x = -1
    debug_assert(
    x > 0, String.format_sequence(“expected x to be more than 0 but got:, x)
    )
    x = -1
    debug_assert(
    x > 0, String.format_sequence(“expected x to be more than 0 but got:, x)
    )

    Which can't be optimized away by the compiler in release builds, you can now pass multiple args for a formatted message at no runtime cost:

    debug_assert(x > 0, “expected x to be more than 0 but got:, x)
    debug_assert(x > 0, “expected x to be more than 0 but got:, x)
  • Automatic parameterization of parameters is now supported. Specifying a parameterized type with unbound parameters causes them to be implicitly added to the function signature as infer-only parameters.

    fn foo[value: SIMD[DType.int32, _]]():
    pass

    # Equivalent to
    fn foo[size: Int, //, value: SIMD[DType.int32, size]]():
    pass
    fn foo[value: SIMD[DType.int32, _]]():
    pass

    # Equivalent to
    fn foo[size: Int, //, value: SIMD[DType.int32, size]]():
    pass
  • Mojo can now interpret simple LLVM intrinsics in parameter expressions, enabling things like count_leading_zeros to work at compile time: Issue #933.

  • Introduced the @explicit_destroy annotation, the __disable_del keyword, the UnknownDestructibility trait, and the ImplicitlyDestructible keyword, for the experimental explicitly destroyed types feature.

  • Added associated types; we can now have aliases like alias T: AnyType, alias N: Int, etc. in a trait, and then specify them in structs that conform to that trait. For more information, see Associated aliases for generics.

Standard library changes

  • Introduced a new Deque (double-ended queue) collection type, based on a dynamically resizing circular buffer for efficient O(1) additions and removals at both ends as well as O(1) direct access to all elements.

    The Deque supports the full Python collections.deque API, ensuring that all expected deque operations perform as in Python.

    Enhancements to the standard Python API include peek() and peekleft() methods for non-destructive access to the last and first elements, and advanced constructor options (capacity, min_capacity, and shrink) for customizing memory allocation and performance. These options allow for optimized memory usage and reduced buffer reallocations, providing flexibility based on application requirements.

  • The Formatter struct has been replaced with a Writer trait to enable buffered IO, increasing print and file writing perf to the same speed as C. It's now more general purpose and can write any Span[Byte]. To align with this the Formattable trait is now named Writable, and the String.format_sequence() static method to initialize a new String has been renamed to String.write(). Here's an example of using all of the changes:

    from memory import Span

    @value
    struct NewString(Writer, Writable):
    var s: String

    # Writer requirement to write a Span of Bytes
    fn write_bytes(inout self, bytes: Span[Byte, _]):
    self.s._iadd[False](bytes)

    # Writer requirement to take multiple args
    fn write[*Ts: Writable](inout self, *args: *Ts):
    @parameter
    fn write_arg[T: Writable](arg: T):
    arg.write_to(self)

    args.each[write_arg]()

    # Also make it Writable to allow `print` to write the inner String
    fn write_to[W: Writer](self, inout writer: W):
    writer.write(self.s)


    @value
    struct Point(Writable):
    var x: Int
    var y: Int

    # Pass multiple args to the Writer. The Int and StringLiteral types call
    # `writer.write_bytes` in their own `write_to` implementations.
    fn write_to[W: Writer](self, inout writer: W):
    writer.write("Point(", self.x, ", ", self.y, ")")

    # Enable conversion to a String using `str(point)`
    fn __str__(self) -> String:
    return String.write(self)


    fn main():
    var point = Point(1, 2)
    var new_string = NewString(str(point))
    new_string.write("\n", Point(3, 4))
    print(new_string)
    from memory import Span

    @value
    struct NewString(Writer, Writable):
    var s: String

    # Writer requirement to write a Span of Bytes
    fn write_bytes(inout self, bytes: Span[Byte, _]):
    self.s._iadd[False](bytes)

    # Writer requirement to take multiple args
    fn write[*Ts: Writable](inout self, *args: *Ts):
    @parameter
    fn write_arg[T: Writable](arg: T):
    arg.write_to(self)

    args.each[write_arg]()

    # Also make it Writable to allow `print` to write the inner String
    fn write_to[W: Writer](self, inout writer: W):
    writer.write(self.s)


    @value
    struct Point(Writable):
    var x: Int
    var y: Int

    # Pass multiple args to the Writer. The Int and StringLiteral types call
    # `writer.write_bytes` in their own `write_to` implementations.
    fn write_to[W: Writer](self, inout writer: W):
    writer.write("Point(", self.x, ", ", self.y, ")")

    # Enable conversion to a String using `str(point)`
    fn __str__(self) -> String:
    return String.write(self)


    fn main():
    var point = Point(1, 2)
    var new_string = NewString(str(point))
    new_string.write("\n", Point(3, 4))
    print(new_string)
    Point(1, 2)
    Point(3, 4)
    Point(1, 2)
    Point(3, 4)
  • Python interop changes:

    • Introduced TypedPythonObject as a light-weight way to annotate PythonObject values with static type information. This design will likely evolve and change significantly.

      • Added TypedPythonObject["Tuple].__getitem__() for accessing the elements of a Python tuple.
    • Added Python.add_object(), to add a named PythonObject value to a Python 'module' object instance.

    • Added Python.unsafe_get_python_exception(), as an efficient low-level utility to get the Mojo Error equivalent of the current CPython error state.

    • Add PythonObject.from_borrowed_ptr(), to simplify the construction of PythonObject values from CPython 'borrowed reference' pointers.

      The existing PythonObject.__init__(PyObjectPtr) should continue to be used for the more common case of constructing a PythonObject from a 'strong reference' pointer.

    • Support for multi-dimensional indexing and slicing for PythonObject (PR #3549, PR #3583).

      var np = Python.import_module("numpy")
      var a = np.array(PythonObject([1,2,3,4,5,6])).reshape(2,3)
      print((a[0, 1])) # 2
      print((a[1][::-1])) # [6 5 4]
      var np = Python.import_module("numpy")
      var a = np.array(PythonObject([1,2,3,4,5,6])).reshape(2,3)
      print((a[0, 1])) # 2
      print((a[1][::-1])) # [6 5 4]

      Note that the syntax, a[1, ::-1], is currently not supported.

    • Added PythonObject.__contains__(). (PR #3101)

      Example usage:

      x = PythonObject([1,2,3])
      if 1 in x:
      print("1 in x")
      x = PythonObject([1,2,3])
      if 1 in x:
      print("1 in x")
  • Pointer related changes:

    • The UnsafePointer type now has an origin parameter that can be used when the UnsafePointer points to a value with a known origin. This origin is propagated through the ptr[] indirection operation. This parameter and other UnsafePointer parameters (other than the type) are now keyword-only.

    • You can now index into UnsafePointer using SIMD scalar integral types:

      p = UnsafePointer[Int].alloc(1)
      i = UInt8(1)
      p[i] = 42
      print(p[i])
      p = UnsafePointer[Int].alloc(1)
      i = UInt8(1)
      p[i] = 42
      print(p[i])
    • Added a new OwnedPointer type as a safe, single-owner, non-nullable smart pointer with similar semantics to Rust's Box<> and C++'s std::unique_ptr. (PR #3524)

    • Arc has been renamed to ArcPointer, for consistency with OwnedPointer.

    • ArcPointer now implements Identifiable, and can be compared for pointer equivalence using a is b.

    • The Reference type has been renamed to Pointer: a memory safe complement to UnsafePointer. This change is motivated by the fact that Pointer is assignable and requires an explicit dereference with ptr[]. Renaming to Pointer clarifies that "references" means ref arguments and results, and gives us a model that is more similar to what the C++ community would expect.

      For an overview of Mojo's pointer types, see the new Intro to pointers page in the Mojo Manual.

    • A new as_noalias_ptr() method as been added to UnsafePointer. This method specifies to the compiler that the resultant pointer is a distinct identifiable object that does not alias any other memory in the local scope.

  • Added the Floatable and FloatableRaising traits to denote types that can be converted to a Float64 value using the builtin float function. Made SIMD and FloatLiteral conform to the Floatable trait. (PR #3163)

    fn foo[F: Floatable](v: F):
    ...

    var f = float(Int32(45))
    fn foo[F: Floatable](v: F):
    ...

    var f = float(Int32(45))
  • The rebind() standard library function now works with memory-only types in addition to @register_passable("trivial") ones, without requiring a copy. For more information, see The rebind() builtin in the Mojo Manual.

  • Introduced the random.shuffle() function for randomizing the elements of a List. (PR #3327)

    Example:

    from random import shuffle

    var l = List[Int](1, 2, 3, 4, 5)
    shuffle(l)
    from random import shuffle

    var l = List[Int](1, 2, 3, 4, 5)
    shuffle(l)
  • The Dict.__getitem__() method now returns a reference instead of a copy of the value (or raises). This improves the performance of common code that uses Dict by allowing borrows from the Dict elements.

  • Slice.step is now an Optional[Int], matching the optionality of slice.step in Python. (PR #3160)

  • There is now a Byte alias to better express intent when working with a pack of bits. (PR #3670).

  • Expanded os.path with new functions:

    • os.path.expandvars(): Expands environment variables in a path (PR #3735).
    • os.path.splitroot(): Split a path into drive, root and tail. (PR #3780).
  • Added a reserve() method and new constructor to the String struct to allocate additional capacity. (PR #3755).

  • A new StringLiteral.get[some_stringable]() method is available. It allows forming a runtime-constant StringLiteral from a compile-time-dynamic Stringable value.

  • Span has moved from the utils module to the memory module.

  • Span now implements __reversed__(). This means that one can get a reverse iterator over a Span using reversed(my_span). Users should currently prefer this method over my_span[::-1].

  • A new AsBytes trait has been added to enable taking a Span[Byte] from any type that implements as_bytes(). String.as_bytes() and String.as_bytes_slice() have been consolidated under String.as_bytes() to return a Span[Byte]. If you require a copy, you can convert the Span to a List with List(my_string.as_bytes()).

  • StringSlice now implements strip(), rstrip(), and lstrip().

  • StringRef now implements split() which can be used to split a StringRef into a List[StringRef] by a delimiter. (PR #2705)

  • StringRef is now representable so repr(StringRef("hello")) will return StringRef('hello').

  • More things have been removed from the auto-exported set of entities in the prelude module from the Mojo standard library:

    • UnsafePointer has been removed. Please explicitly import it via from memory import UnsafePointer.
    • StringRef has been removed. Please explicitly import it via from utils import StringRef.
  • Restored implicit copyability of Tuple and ListLiteral.

  • The aliases for C foreign function interface (FFI) have been renamed: C_int -> c_int, C_long -> c_long and so on.

  • Float32 and Float64 are now printed and converted to strings with roundtrip guarantee and shortest representation:

    Value                       Old                       New
    Float64(0.3) 0.29999999999999999 0.3
    Float32(0.3) 0.30000001192092896 0.3
    Float64(0.0001) 0.0001 0.0001
    Float32(0.0001) 9.9999997473787516e-05 0.0001
    Float64(-0.00001) -1.0000000000000001e-05 -1e-05
    Float32(-0.00001) -9.9999997473787516e-06 -1e-05
    Float32(0.00001234) 1.2339999557298142e-05 1.234e-05
    Float32(-0.00000123456) -1.2345600453045336e-06 -1.23456e-06
    Float64(1.1234567e-320) 1.1235052786429946e-320 1.1235e-320
    Float64(1.234 * 10**16) 12340000000000000.0 1.234e+16
    Value                       Old                       New
    Float64(0.3) 0.29999999999999999 0.3
    Float32(0.3) 0.30000001192092896 0.3
    Float64(0.0001) 0.0001 0.0001
    Float32(0.0001) 9.9999997473787516e-05 0.0001
    Float64(-0.00001) -1.0000000000000001e-05 -1e-05
    Float32(-0.00001) -9.9999997473787516e-06 -1e-05
    Float32(0.00001234) 1.2339999557298142e-05 1.234e-05
    Float32(-0.00000123456) -1.2345600453045336e-06 -1.23456e-06
    Float64(1.1234567e-320) 1.1235052786429946e-320 1.1235e-320
    Float64(1.234 * 10**16) 12340000000000000.0 1.234e+16
  • The StaticIntTuple data structure in the utils package has been renamed to IndexList. The data structure now allows one to specify the index bitwidth of the elements along with whether the underlying indices are signed or unsigned.

  • Added DLHandle.get_symbol(), for getting a pointer to a symbol in a dynamic library. This is more general purpose than the existing methods for getting function pointers.

Tooling changes

  • The VS Code Mojo Debugger now has a buildArgs JSON debug configuration setting that can be used in conjunction with mojoFile to define the build arguments when compiling the Mojo file.

  • The VS Code extension now supports a Configure Build and Run Args command that helps set the build and run args for actions file Run Mojo File and Debug Mojo File. A corresponding button appears in Run and Debug selector in the top right corner of a Mojo File.

  • The VS Code extension now has the mojo.run.focusOnTerminalAfterLaunch setting, which controls whether to focus on the terminal used by the Mojo: Run Mojo File command or on the editor after launch. Issue #3532.

  • The VS Code extension now has the mojo.SDK.additionalSDKs setting, which allows the user to provide a list of MAX SDKs that the extension can use when determining a default SDK to use. The user can select the default SDK to use with the Mojo: Select the default MAX SDK command.

  • The VS Code extension now supports setting data breakpoints as well as function breakpoints.

  • The Mojo LLDB debugger now supports symbol breakpoints, for example, b main or b my_module::main.

  • Error messages that include type names no longer include inferred or defaulted parameters when they aren't needed. For example, previously Mojo complained about things like:

    ... cannot be converted from 'UnsafePointer[UInt, 0, _default_alignment::AnyType](), MutableAnyOrigin]' to 'UnsafePointer[Int, 0, _default_alignment[::AnyType](), MutableAnyOrigin]'
    ... cannot be converted from 'UnsafePointer[UInt, 0, _default_alignment::AnyType](), MutableAnyOrigin]' to 'UnsafePointer[Int, 0, _default_alignment[::AnyType](), MutableAnyOrigin]'

    it now complains more helpfully that:

    ... cannot be converted from 'UnsafePointer[UInt]' to 'UnsafePointer[Int]'
    ... cannot be converted from 'UnsafePointer[UInt]' to 'UnsafePointer[Int]'
  • Tooling now prints the origins of ref arguments and results correctly, and prints self instead of self: Self in methods.

  • The Mojo Language Server and generated documentation now print parametric result types correctly, e.g. showing SIMD[type, simd_width] instead of SIMD[$0, $1].

  • Generated API documentation now shows the signatures for structs, and identifies @register_passable and @register_passable("trivial") types.

  • The VS Code extension now allows cancelling the installation of its private MAX SDK.

  • The VS Code extension now opens the Run and Debug tab automatically whenever a debug session starts.

  • The mojo debug --vscode command now support the --init-command and --stop-on-entry flags. Execute mojo debug --help for more information.

  • The Mojo LLDB debugger on VS Code now supports inspecting the raw attributes of variables that are handled as synthetic types, e.g. List from Mojo or std::vector from C++.

  • The VS Code extension now allows selecting a default SDK when multiple are available.

❌ Removed

  • The UnsafePointer.bitcast() overload for DType has been removed. Wrap your DType in a Scalar[my_dtype] to call the only overload of bitcast() now.

🛠️ Fixed

  • Lifetime tracking is now fully field sensitive, which makes the uninitialized variable checker more precise.

  • Issue #1310 - Mojo permits the use of any constructor for implicit conversions

  • Issue #1632 - Mojo produces weird error when inout function is used in non mutating function

  • Issue #3444 - Raising init causing use of uninitialized variable

  • Issue #3544 - Known mutable ref argument are not optimized as noalias by LLVM.

  • Issue #3559 - VariadicPack doesn't extend the lifetimes of the values it references.

  • Issue #3627 - Compiler overlooked exclusivity violation caused by ref [MutableAnyOrigin] T

  • Issue #3710 - Mojo frees memory while reference to it is still in use.

  • Issue #3805 - Crash When Initializing !llvm.ptr.

  • Issue #3816 - Ternary if-operator doesn't propagate origin information.

  • Issue #3815 - [BUG] Mutability not preserved when taking the union of two origins.

  • Issue #3829 - Poor error message when invoking a function pointer upon an argument of the wrong origin

  • Issue #3830 - Failures emitting register RValues to ref arguments.

  • The VS Code extension now auto-updates its private copy of the MAX SDK.

  • The variadic initializer for SIMD now works in parameter expressions.

  • The VS Code extension now downloads its private copy of the MAX SDK in a way that prevents ETXTBSY errors on Linux.

  • The VS Code extension now allows invoking a mojo formatter from SDK installations that contain white spaces in their path.

Special thanks

Special thanks to our community contributors: @soraos, @jjvraw, @bgreni, @thatstoasty, @szbergeron, @rd4com, @fknfilewalker, @gabrieldemarmiesse, @avitkauskas, and @martinvuyk.

v24.5 (2024-09-13)

✨ Highlights

Here's a brief summary of some of the major changes in this release, with more detailed information in the following sections:

  • Mojo now supports Python 3.12 interoperability.

  • The set of automatically imported entities (types, aliases, functions) into users' Mojo programs has been dramatically reduced. This can break existing user code as users will need to explicitly import what they're using for cases previously automatically included before.

  • print() now requires that its arguments conform to the Formattable trait. This enables efficient stream-based writing by default, avoiding unnecessary intermediate String heap allocations.

  • The new builtin input() function prints an optional prompt and reads a line from standard input, in the same way as Python.

  • Mojo now allows implicit definitions of variables within a fn in the same way that has been allowed in a def. The var keyword is still allowed, but is now optional.

  • Mojo now diagnoses "argument exclusivity" violations due to aliasing references. Mojo requires references (including implicit references due to borrowed/inout arguments) to be uniquely referenced (non-aliased) if mutable. This is a warning in the 24.5 release, but will be upgraded to an error in subsequent releases.

  • Mojo now supports "conditional conformances" where some methods on a struct have additional trait requirements that the struct itself doesn't.

  • DTypePointer, LegacyPointer, and Pointer have been removed. Use UnsafePointer instead. Functions that previously took a DTypePointer now take an equivalent UnsafePointer. For more information on using pointers, see Unsafe pointers in the Mojo Manual.

  • There are many new standard library APIs, with new features for strings, collections, and interacting with the filesystem and environment. Changes are listed in the standard library section.

  • The VS Code extension now supports a vendored MAX SDK for VS Code, which is automatically downloaded by the extension and it's used for all Mojo features, including the Mojo Language Server, the Mojo debugger, the Mojo formatter, and more.

  • mojo test now uses the Mojo compiler for running unit tests. This will resolve compilation issues that sometimes appeared, and will also improve overall test execution times.

Language changes

  • Mojo now allows implicit definitions of variables within a fn in the same way that has been allowed in a def. The var keyword is still allowed and still denotes the declaration of a new variable with a scope (in both def and fn). Relaxing this makes fn and def more similar, but they still differ in other important ways.

  • Mojo now diagnoses "argument exclusivity" violations due to aliasing references. Mojo requires references (including implicit references due to borrowed/inout arguments) to be uniquely referenced (non-aliased) if mutable. This is important for code safety, because it allows the compiler (and readers of code) to understand where and when a value is mutated. It is also useful for performance optimization because it allows the compiler to know that accesses through immutable references cannot change behind the scenes. Here is an invalid example:

    fn take_two_strings(a: String, inout b: String):
    # Mojo knows 'a' and 'b' cannot be the same string.
    b += a

    fn invalid_access():
    var my_string = String()

    # warning: passing `my_string` inout is invalid since it is also passed
    # borrowed.
    take_two_strings(my_string, my_string)
    fn take_two_strings(a: String, inout b: String):
    # Mojo knows 'a' and 'b' cannot be the same string.
    b += a

    fn invalid_access():
    var my_string = String()

    # warning: passing `my_string` inout is invalid since it is also passed
    # borrowed.
    take_two_strings(my_string, my_string)

    This is similar to Swift exclusivity checking and the Rust language sometimes known as "aliasing xor mutability". That said, the Mojo implementation details are somewhat different because lifetimes are embedded in types.

    This is a warning in the 24.5 release, but will be upgraded to an error in subsequent releases.

  • Mojo now supports "conditional conformances" where some methods on a struct have additional trait requirements that the struct itself doesn't. This is expressed through an explicitly declared self type:

    struct GenericThing[Type: AnyType]:  # Works with anything
    # Sugar for 'fn normal_method[Type: AnyType](self: GenericThing[Type]):'
    fn normal_method(self): ...

    # Just redeclare the requirements with more specific types:
    fn needs_move[Type: Movable](self: GenericThing[Type], owned val: Type):
    var tmp = val^ # Ok to move 'val' since it is Movable
    ...
    fn usage_example():
    var a = GenericThing[Int]()
    a.normal_method() # Ok, Int conforms to AnyType
    a.needs_move(42) # Ok, Int is movable

    var b = GenericThing[NonMovable]()
    b.normal_method() # Ok, NonMovable conforms to AnyType

    # error: argument type 'NonMovable' does not conform to trait 'Movable'
    b.needs_move(NonMovable())
    struct GenericThing[Type: AnyType]:  # Works with anything
    # Sugar for 'fn normal_method[Type: AnyType](self: GenericThing[Type]):'
    fn normal_method(self): ...

    # Just redeclare the requirements with more specific types:
    fn needs_move[Type: Movable](self: GenericThing[Type], owned val: Type):
    var tmp = val^ # Ok to move 'val' since it is Movable
    ...
    fn usage_example():
    var a = GenericThing[Int]()
    a.normal_method() # Ok, Int conforms to AnyType
    a.needs_move(42) # Ok, Int is movable

    var b = GenericThing[NonMovable]()
    b.normal_method() # Ok, NonMovable conforms to AnyType

    # error: argument type 'NonMovable' does not conform to trait 'Movable'
    b.needs_move(NonMovable())

    Conditional conformance works with dunder methods and other things as well.

  • As a specific form of "conditional conformances", initializers in a struct may indicate specific parameter bindings to use in the type of their self argument. For example:

    @value
    struct MyStruct[size: Int]:
    fn __init__(inout self: MyStruct[0]): pass
    fn __init__(inout self: MyStruct[1], a: Int): pass
    fn __init__(inout self: MyStruct[2], a: Int, b: Int): pass

    def test(x: Int):
    a = MyStruct() # Infers size=0 from 'self' type.
    b = MyStruct(x) # Infers size=1 from 'self' type.
    c = MyStruct(x, x) # Infers size=2 from 'self' type.
    @value
    struct MyStruct[size: Int]:
    fn __init__(inout self: MyStruct[0]): pass
    fn __init__(inout self: MyStruct[1], a: Int): pass
    fn __init__(inout self: MyStruct[2], a: Int, b: Int): pass

    def test(x: Int):
    a = MyStruct() # Infers size=0 from 'self' type.
    b = MyStruct(x) # Infers size=1 from 'self' type.
    c = MyStruct(x, x) # Infers size=2 from 'self' type.
  • Mojo now supports named result bindings. Named result bindings are useful for directly emplacing function results into the output slot of a function. This feature provides more flexibility and guarantees around emplacing the result of a function compared to "guaranteed" named return value optimization (NRVO). If a @register_passable result is bound to a name, the result value is made accessible as a mutable reference.

    fn efficiently_return_string(b: Bool) -> String as output:
    if b:
    output = "emplaced!"
    mutate(output)
    return
    return "regular return"
    fn efficiently_return_string(b: Bool) -> String as output:
    if b:
    output = "emplaced!"
    mutate(output)
    return
    return "regular return"

    If we used a temporary for output instead, we would need to move into the result slot, which wouldn't work if the result type was non-movable.

    In a function with a named result, return may be used with no operand to signal an exit from the function, or it can be used normally to specify the return value of the function. The compiler will error if the result is not initialized on all normal exit paths from the function.

  • __setitem__() now works with variadic argument lists such as:

    struct YourType:
    fn __setitem__(inout self, *indices: Int, val: Int): ...
    struct YourType:
    fn __setitem__(inout self, *indices: Int, val: Int): ...

    The Mojo compiler now always passes the "new value" being set using the last keyword argument of the __setitem__(), e.g. turning yourType[1, 2] = 3 into yourType.__setitem__(1, 2, val=3). This fixes Issue #248.

  • Mojo context managers used in regions of code that may raise no longer need to define a "conditional" exit function in the form of fn __exit__(self, e: Error) -> Bool. This function allows the context manager to conditionally intercept and handle the error and allow the function to continue executing. This is useful for some applications, but in many cases the conditional exit would delegate to the unconditional exit function fn __exit__(self).

    Concretely, this enables defining with regions that unconditionally propagate inner errors, allowing code like:

    def might_raise() -> Int:
    ...

    def foo() -> Int:
    with ContextMgr():
    return might_raise()
    # no longer complains about missing return

    def bar():
    var x: Int
    with ContextMgr():
    x = might_raise()
    print(x) # no longer complains about 'x' being uninitialized
    def might_raise() -> Int:
    ...

    def foo() -> Int:
    with ContextMgr():
    return might_raise()
    # no longer complains about missing return

    def bar():
    var x: Int
    with ContextMgr():
    x = might_raise()
    print(x) # no longer complains about 'x' being uninitialized
  • async functions now support memory-only results (like String, List, etc.) and raises. Accordingly, both Coroutine and RaisingCoroutine have been changed to accept AnyType instead of AnyTrivialRegType. This means the result types of async functions do not need to be Movable.

    async fn raise_or_string(c: Bool) raises -> String:
    if c:
    raise "whoops!"
    return "hello world!"
    async fn raise_or_string(c: Bool) raises -> String:
    if c:
    raise "whoops!"
    return "hello world!"

    Note that async functions do not yet support indirect calls, ref results, and constructors.

  • The Reference type (and many iterators) now use infer-only parameters to represent the mutability of their lifetime, simplifying the interface.

  • The environment variable MOJO_PYTHON can be pointed to an executable to pin Mojo to a specific version:

    export MOJO_PYTHON="/usr/bin/python3.11"
    export MOJO_PYTHON="/usr/bin/python3.11"

    Or a virtual environment to always have access to those Python modules:

    export MOJO_PYTHON="~/venv/bin/python"
    export MOJO_PYTHON="~/venv/bin/python"

    MOJO_PYTHON_LIBRARY still exists for environments with a dynamic libpython but no Python executable.

  • The pointer aliasing semantics of Mojo have changed. Initially, Mojo adopted a C-like set of semantics around pointer aliasing and derivation. However, the C semantics bring a lot of history and baggage that are not needed in Mojo and which complicate compiler optimizations. The language overall provides a stronger set of invariants around pointer aliasing with lifetimes and exclusive mutable references to values, etc.

    It is now forbidden to convert a non-pointer-typed value derived from a Mojo-allocated pointer, such as an integer address, to a pointer-typed value. "Derived" means there is overlap in the bits of the non-pointer-typed value with the original pointer value. Accordingly, the UnsafePointer constructor that took an address keyword argument has been removed.

    It is still possible to make this conversion in certain cases where it is absolutely necessary, such as interoperating with other languages like Python. In this case, the compiler makes two assumptions: any pointer derived from a non-pointer-typed value does not alias any Mojo-derived pointer and that any external function calls have arbitrary memory effects.

  • await on a coroutine now consumes it. This strengthens the invariant that coroutines can be awaited only once.

Standard library changes

  • builtin package:

    • The set of automatically imported entities (types, aliases, functions) into users' Mojo programs has been dramatically reduced. Before, with the way the builtin module was handled, all of the entities in the following modules would be automatically included:

      memory, sys, os, utils, python, bit, random, math, builtin, collections

      Now, only the explicitly enumerated entities in prelude/__init__.mojo are the ones automatically imported into users' Mojo programs. This will break a lot of user code as users will need to explicitly import what they're using for cases previously commonly included before (such as Optional, Variant, and functions such as abort(), alignof(), bitcast(), bitwidthof(), external_call(), simdwidthof(), and sizeof()).

    • Some types from the builtin module have been moved to different modules for clarity which is made possible now that we have a prelude module that can re-export symbols from modules other than builtin.

      In particular, the builtin.string module has been moved to collections.string.

  • Input and output:

    • Added the builtin input() function, which behaves the same as Python. (PR #3392)

      name = input("Enter your name: ")
      print("Hello, " + name + "!")
      name = input("Enter your name: ")
      print("Hello, " + name + "!")

      If the user enters "Mojo" it returns "Hello, Mojo!"

      There is a known issue when running the input() function with JIT compilation (see issue #3479).

    • print() now requires that its arguments conform to the Formattable trait. This enables efficient stream-based writing by default, avoiding unnecessary intermediate String heap allocations.

      Previously, print() required types conform to Stringable. This meant that to execute a call like print(a, b, c), at least three separate String heap allocations were down, to hold the formatted values of a, b, and c respectively. The total number of allocations could be much higher if, for example, a.__str__() was implemented to concatenate together the fields of a, like in the following example:

      struct Point(Stringable):
      var x: Float64
      var y: Float64

      fn __str__(self) -> String:
      # Performs 3 allocations: 1 each for str(..) of each of the fields,
      # and then the final returned `String` allocation.
      return "(" + str(self.x) + ", " + str(self.y) + ")"
      struct Point(Stringable):
      var x: Float64
      var y: Float64

      fn __str__(self) -> String:
      # Performs 3 allocations: 1 each for str(..) of each of the fields,
      # and then the final returned `String` allocation.
      return "(" + str(self.x) + ", " + str(self.y) + ")"

      A type like the one above can transition to additionally implementing Formattable with the following changes:

      struct Point(Stringable, Formattable):
      var x: Float64
      var y: Float64

      fn __str__(self) -> String:
      return String.format_sequence(self)

      fn format_to(self, inout writer: Formatter):
      writer.write("(", self.x, ", ", self.y, ")")
      struct Point(Stringable, Formattable):
      var x: Float64
      var y: Float64

      fn __str__(self) -> String:
      return String.format_sequence(self)

      fn format_to(self, inout writer: Formatter):
      writer.write("(", self.x, ", ", self.y, ")")

      In the example above, String.format_sequence() is used to construct a String from a type that implements Formattable. This pattern of implementing a type's Stringable implementation in terms of its Formattable implementation minimizes boilerplate and duplicated code, while retaining backwards compatibility with the requirements of the commonly used str() function.

    • debug_assert() now also requires that its message argument conform to Formattable.

    • Added TemporaryDirectory in module tempfile. (PR 2743)

    • Added NamedTemporaryFile in module tempfile. (PR 2762)

  • String and friends:

    • The builtin.string module has been moved to collections.string.

    • Added the String.format() method. (PR #2771)

      Supports automatic and manual indexing of *args.

      Examples:

      print(
      String("{1} Welcome to {0} {1}").format("mojo", "🔥")
      )
      # 🔥 Wecome to mojo 🔥
      print(
      String("{1} Welcome to {0} {1}").format("mojo", "🔥")
      )
      # 🔥 Wecome to mojo 🔥
      print(String("{} {} {}").format(True, 1.125, 2))
      #True 1.125 2
      print(String("{} {} {}").format(True, 1.125, 2))
      #True 1.125 2
    • String.format() now supports conversion flags !s and !r, allowing for str() and repr() conversions within format strings. (PR #3279)

      Example:

      String("{} {!r}").format("Mojo", "Mojo")
      # "Mojo 'Mojo'"

      String("{0!s} {0!r}").format("Mojo")
      # "Mojo 'Mojo'"
      String("{} {!r}").format("Mojo", "Mojo")
      # "Mojo 'Mojo'"

      String("{0!s} {0!r}").format("Mojo")
      # "Mojo 'Mojo'"
    • The String class now has rjust(), ljust(), and center() methods to return a justified string based on width and fillchar. (PR #3278)

    • The atol() function now correctly supports leading underscores, (e.g.atol("0x_ff", 0)), when the appropriate base is specified or inferred (base 0). non-base-10 integer literals as per Python's Integer Literals. (PR #3180)

    • Added the unsafe_cstr_ptr() method to String and StringLiteral, which returns an UnsafePointer[c_char] for convenient interoperability with C APIs.

    • Added the byte_length() method to String, StringSlice, and StringLiteral and deprecated their private _byte_length() methods. Added a warning to the String.__len__() method that it will return the length in Unicode codepoints in the future and StringSlice.__len__() now does return the Unicode codepoints length. (PR #2960)

    • Added a new StaticString type alias. This can be used in place of StringLiteral for runtime string arguments.

    • Added a StringSlice initializer that accepts a StringLiteral.

    • The StringRef constructors from DTypePointer.int8 have been changed to take a UnsafePointer[c_char], reflecting their use for compatibility with C APIs.

    • Continued the transition to UnsafePointer and unsigned byte type for strings:

  • UnsafePointer and other reference type changes:

    • DTypePointer, LegacyPointer, and Pointer have been removed. Use UnsafePointer instead. For more information on using pointers, see Unsafe pointers in the Mojo Manual.

      Functions that previously took a DTypePointer now take an equivalent UnsafePointer. A quick rule for conversion from DTypePointer to UnsafePointer is:

      DTypePointer[type] -> UnsafePointer[Scalar[type]]
      DTypePointer[type] -> UnsafePointer[Scalar[type]]

      There could be places that you have code of the form:

      fn f(ptr: DTypePointer):
      fn f(ptr: DTypePointer):

      which is equivalent to DTypePointer[*_]. In this case you would have to add an infer-only type parameter to the function:

      fn f[type: DType, //](ptr: UnsafePointer[Scalar[type]]):
      fn f[type: DType, //](ptr: UnsafePointer[Scalar[type]]):

      because we can’t have an unbound parameter inside the struct.

      There could also be places where you use DTypePointer[Scalar[DType.invalid/index]], and it would be natural to change these to UnsafePointer[NoneType/Int]. But since these are not an UnsafePointer that stores a Scalar, you might have to rebind/bitcast to appropriate types.

    • The DTypePointer load() and store() methods have been moved to UnsafePointer.

    • UnsafePointer now supports strided_load(), strided_store(), gather(), and scatter() when the underlying type is Scalar[DType].

    • The global functions for working with UnsafePointer have transitioned to being methods through the use of conditional conformances:

    • The UnsafePointer.offset() method is deprecated and will be removed in a future release. Use pointer arithmetic instead.

      new_ptr = ptr.offset(1)
      new_ptr = ptr.offset(1)

      Becomes:

      new_ptr = ptr + 1
      new_ptr = ptr + 1
    • UnsafePointer now has an alignment parameter to specify the static alignment of the pointer. Consequently, UnsafePointer.alloc() no longer takes in an alignment parameter, and the alignment should be specified in the type.

      UnsafePointer[type].alloc[alignment](x) # now becomes
      UnsafePointer[type, alignment].alloc(x)
      UnsafePointer[type].alloc[alignment](x) # now becomes
      UnsafePointer[type, alignment].alloc(x)
    • UnsafePointer has a new exclusive: Bool = False parameter. Setting this parameter to true tells the compiler that the user knows this pointer and all those derived from it have exclusive access to the underlying memory allocation. The compiler is not guaranteed to do anything with this information.

    • It is no longer possible to cast (implicitly or explicitly) from Reference to UnsafePointer. Instead of UnsafePointer(someRef) please use the UnsafePointer.address_of(someRef[]) which makes the code explicit that the UnsafePointer gets the address of what the reference points to.

  • Python interoperability changes:

    • Mojo now supports Python 3.12 interoperability.

    • Creating a nested PythonObject from a list or tuple of Python objects is possible now:

      var np = Python.import_module("numpy")
      var a = np.array([1, 2, 3])
      var b = np.array([4, 5, 6])
      var arrays = PythonObject([a, b])
      assert_equal(len(arrays), 2)
      var np = Python.import_module("numpy")
      var a = np.array([1, 2, 3])
      var b = np.array([4, 5, 6])
      var arrays = PythonObject([a, b])
      assert_equal(len(arrays), 2)

      Also allowing more convenient call syntax:

      var stacked = np.hstack((a, b))
      assert_equal(str(stacked), "[1 2 3 4 5 6]")
      var stacked = np.hstack((a, b))
      assert_equal(str(stacked), "[1 2 3 4 5 6]")

      (PR #3264)

    • Accessing local Python modules with Python.add_to_path(".") is no longer required. It now behaves the same as Python. You can access modules in the same folder as the target file:

      • mojo run /tmp/main.mojo can access /tmp/mymodule.py

      • mojo build main.mojo -o ~/myexe && ~/myexe can access ~/mymodule.py

  • Collections:

    • List values are now equality comparable with == and != when their element type is equality comparable. (PR #3195)

    • Optional values are now equality comparable with == and != when their element type is equality comparable.

    • Added a new Counter dictionary-like type, matching most of the features of the Python one. (PR #2910)

    • Dict now implements setdefault(), which gets a value from the dictionary by key, or sets it to a default if it doesn't exist. (PR #2803)

    • Dict now supports popitem(), which removes and returns the last item in the Dict. (PR #2701)

    • Added a Dict.__init__() overload to specify initial capacity. (PR #3171)

      The capacity has to be a power of two and greater than or equal to 8.

      It allows for faster initialization by skipping incremental growth steps.

      Example:

      var dictionary = Dict[Int,Int](power_of_two_initial_capacity = 1024)
      # Insert (2/3 of 1024) entries
      var dictionary = Dict[Int,Int](power_of_two_initial_capacity = 1024)
      # Insert (2/3 of 1024) entries
    • ListLiteral now supports __contains__(). (PR #3251)

  • Filesystem and environment utilities:

    • Path.home() has been added to return a path of the user's home directory.

    • os.path.expanduser() and pathlib.Path.exapanduser() have been added to allow expanding a prefixed ~ in a String or Path with the user's home path:

      import os
      print(os.path.expanduser("~/.modular"))
      # /Users/username/.modular
      print(os.path.expanduser("~root/folder"))
      # /var/root/folder (on macos)
      # /root/folder (on linux)
      import os
      print(os.path.expanduser("~/.modular"))
      # /Users/username/.modular
      print(os.path.expanduser("~root/folder"))
      # /var/root/folder (on macos)
      # /root/folder (on linux)
    • os.path.split() has been added for splitting a path into head, tail:

      import os
      head, tail = os.path.split("/this/is/head/tail")
      print("head:", head)
      print("tail:", tail)
      # head: /this/is/head
      # tail: tail
      import os
      head, tail = os.path.split("/this/is/head/tail")
      print("head:", head)
      print("tail:", tail)
      # head: /this/is/head
      # tail: tail
    • os.makedirs() and os.removedirs() have been added for creating and removing nested directories:

      import os
      path = os.path.join("dir1", "dir2", "dir3")
      os.path.makedirs(path, exist_ok=True)
      os.path.removedirs(path)
      import os
      path = os.path.join("dir1", "dir2", "dir3")
      os.path.makedirs(path, exist_ok=True)
      os.path.removedirs(path)
    • The pwd module has been added for accessing user information in /etc/passwd on POSIX systems. This follows the same logic as Python:

      import pwd
      import os
      current_user = pwd.getpwuid(os.getuid())
      print(current_user)

      # pwd.struct_passwd(pw_name='jack', pw_passwd='********', pw_uid=501,
      # pw_gid=20, pw_gecos='Jack Clayton', pw_dir='/Users/jack',
      # pw_shell='/bin/zsh')

      print(current_user.pw_uid)

      # 501

      root = pwd.getpwnam("root")
      print(root)

      # pwd.struct_passwd(pw_name='root', pw_passwd='*', pw_uid=0, pw_gid=0,
      # pw_gecos='System Administrator', pw_dir='/var/root', pw_shell='/bin/zsh')
      import pwd
      import os
      current_user = pwd.getpwuid(os.getuid())
      print(current_user)

      # pwd.struct_passwd(pw_name='jack', pw_passwd='********', pw_uid=501,
      # pw_gid=20, pw_gecos='Jack Clayton', pw_dir='/Users/jack',
      # pw_shell='/bin/zsh')

      print(current_user.pw_uid)

      # 501

      root = pwd.getpwnam("root")
      print(root)

      # pwd.struct_passwd(pw_name='root', pw_passwd='*', pw_uid=0, pw_gid=0,
      # pw_gecos='System Administrator', pw_dir='/var/root', pw_shell='/bin/zsh')
  • Other new traits and related features:

    • Added the ExplicitlyCopyable trait to mark types that can be copied explicitly, but which might not be implicitly copyable.

      This supports work to transition the standard library collection types away from implicit copyability, which can lead to unintended expensive copies.

    • Added the Identifiable trait, used to describe types that implement the __is__() and __isnot__() trait methods. (PR #2807)

    • Types conforming to Boolable (that is, those implementing __bool__()) no longer implicitly convert to Bool. A new ImplicitlyBoolable trait is introduced for types where this behavior is desired.

  • Miscellaneous:

    • NoneType is now a normal standard library type, and not an alias for a raw MLIR type.

      Function signatures written as fn() -> NoneType should transition to being written as fn() -> None.

    • Mojo now has a UInt type for modeling unsigned (scalar) integers with a platform-dependent width. UInt implements most arithmetic operations that make sense for integers, with the notable exception of __neg__(). Builtin functions such as min()/max(), as well as math functions like ceildiv(), align_down(), and align_up() are also implemented for UInt.

    • Now that we have a UInt type, use this to represent the return type of a hash. In general, hashes should be an unsigned integer, and can also lead to improved performance in certain cases.

    • Added the c_char type alias in sys.ffi.

    • sort() now supports a stable parameter. It can be called by

      sort[cmp_fn, stable=True](list)
      sort[cmp_fn, stable=True](list)

      The algorithm requires O(N)O(N) auxiliary memory. If extra memory allocation fails, the program crashs.

    • sort() no longer takes LegacyPointer since that type is now removed.

    • Added the oct() builtin function for formatting an integer in octal. (PR #2914)

    • Added the assert_is() and assert_is_not() test functions to the testing module.

    • The math package now includes the pi, e, and tau constants (Closes Issue #2135).

    • The ulp function from numerics has been moved to the math module.

    • bit module now supports bit_reverse(), byte_swap(), and pop_count() for the Int type. (PR #3150)

    • A few bit functions have been renamed for clarity:

    • Slice now uses OptionalReg[Int] for start and end and implements a constructor which accepts optional values. Slice._has_end() has also been removed since a Slice with no end is now represented by an empty Slice.end option. (PR #2495)

        var s = Slice(1, None, 2)
      print(s.start.value()) # must retrieve the value from the optional
        var s = Slice(1, None, 2)
      print(s.start.value()) # must retrieve the value from the optional
    • The rank argument for algorithm.elementwise() is no longer required and is only inferred.

    • The time.now() function has been deprecated. Please use time.perf_counter() or time.perf_counter_ns instead.

    • SIMD construction from Bool has been restricted to DType.bool data type.

Tooling changes

  • mojo test new features and changes:

    • mojo test now uses the Mojo compiler for running unit tests. This will resolve compilation issues that sometimes appeared, and will also improve overall test times, since we will only compile unit tests once before executing all of them.

      These changes do not apply to doctests, due to their different semantics.

    • The mojo test command now accepts a --filter option that will narrow the set of tests collected and executed. The filter string is a POSIX extended regular expression.

    • The mojo test command now supports using the same compilation options as mojo build.

    • You can now debug unit tests using mojo test by passing the --debug flag. Most debug flags are supported; run mojo test --help for a full listing.

      Debugging doctests is not currently supported.

  • Mojo debugger new features and changes:

    • The mojo debug --rpc command has been renamed to mojo debug --vscode, which is now able to manage multiple VS Code windows.

    • The Mojo debugger now supports a break-on-raise command that indicated the debugger to stop at any raise statements. A similar features has been added to the debugger on VS Code.

    • The Mojo debugger now hides the artificial function arguments __result__ and __error__ created by the compiler for Mojo code.

  • VS Code support changes:

    • The VS Code extension now supports a vendored MAX SDK for VS Code, which is automatically downloaded by the extension and it's used for all Mojo features, including the Mojo Language Server, the Mojo debugger, the Mojo formatter, and more.

    • A proxy has been added to the Mojo Language Server on VS Code that handles crashes more gracefully.

  • The Mojo Language Server no longer sets . as a commit character for auto-completion.

❌ Removed

  • Support for the legacy fn __init__(...) -> Self: form has been removed from the compiler, please switch to using fn __init__(inout self, ...): instead.

  • The builtin tensor module has been removed. Identical functionality is available in max.tensor, but it is generally recommended to use structs from the buffer module when possible instead.

  • Removed String.unsafe_uint8_ptr(). String.unsafe_ptr() now returns the same thing.

  • Removed StringLiteral.unsafe_uint8_ptr() and StringLiteral.as_uint8_ptr().

  • Removed SIMD.splat(value: Scalar[type]). Use the constructor for SIMD instead.

  • Removed the SIMD.{add,mul,sub}_with_overflow() methods.

  • Removed the SIMD.min() and SIMD.max() methods. Identical functionality is available using the builtin min() and max() functions.

  • Removed the Mojo Language Server warnings for unused function arguments.

  • Run Mojo File in Dedicated Terminal action has been removed, and the action Run Mojo File will always open a dedicated terminal for each mojo file to guarantee a correct environment.

🛠️ Fixed

  • Fixed a crash in the Mojo Language Server when importing the current file.

  • Fixed crash when specifying variadic keyword arguments without a type expression in def functions, e.g.:

    def foo(**kwargs): ...  # now works
    def foo(**kwargs): ...  # now works
  • Mojo now prints ref arguments and results in generated documentation correctly.

  • #1734 - Calling __copyinit__ on self causes crash.

  • #3142 - [QoI] Confusing __setitem__ method is failing with a "must be mutable" error.

  • #248 - [Feature] Enable __setitem__ to take variadic arguments

  • #3065 - Fix incorrect behavior of SIMD.__int__ on unsigned types

  • #3045 - Disable implicit SIMD conversion routes through Bool

  • #3126 - [BUG] List doesn't work at compile time.

  • #3237 - [BUG] Difference between __getitem__ and [.] operator.

  • #3336 - Fix outdated references to let in REPL documentation.

  • The VS Code extension no longer caches the information of the selected MAX SDK, which was causing issues upon changes in the SDK.

  • The Mojo debugger now stops showing spurious warnings when parsing closures.

Special thanks

Special thanks to our community contributors: @jjvraw, @artemiogr97, @martinvuyk, @jayzhan211, @bgreni, @mzaks, @msaelices, @rd4com, @jiex-liu, @kszucs, @thatstoasty

v24.4 (2024-06-07)

✨ Highlights

Big themes for this release:

  • Improvements to the performance and ease-of-use for def functions.

  • Continued unification of standard library APIs around the UnsafePointer type.

  • Many quality-of-life improvements for the standard library collection types.

  • Significant performance improvements when inserting into a Dict. Performance on this metric is still not where we'd like it to be, but it is much improved.

  • A new @parameter for mechanism for expressing compile-time loops, which replaces the earlier (and less reliable) @unroll decorator.

  • New Mojo Manual pages on Control flow, Testing and using unsafe pointers.

Language changes

  • Mojo has changed how def function arguments are processed. Previously, by default, arguments to a def were treated according to the owned convention, which makes a copy of the value, enabling that value to be mutable in the callee.

    This could lead to major performance issues because of the proliferation of unnecessary copies. It also required you to declare non-copyable types as borrowed explicitly. Now Mojo takes a different approach: def functions take arguments as borrowed by default (consistent with fn functions) but will make a local copy of the value only if the argument is mutated in the body of the function.

    This improves consistency, performance, and ease of use.

  • Implicit variable definitions in a def function are more flexible: you can now implicitly declare variables as the result of a tuple return, using a,b,c = foo(). For example:

    def return_two(i: Int) -> (Int, Int):
    return i, i+1

    a, b = return_two(5)
    def return_two(i: Int) -> (Int, Int):
    return i, i+1

    a, b = return_two(5)

    Implicit variable declarations can also now shadow global immutable symbols (such as module names and built-ins) without getting a compiler error. For example:

    slice = foo()
    slice = foo()
  • Mojo functions can return an auto-dereferenced reference to storage with a new ref keyword in the result type specifier. For example:

    @value
    struct Pair:
    var first: Int
    var second: Int

    fn get_first_ref(inout self) -> ref [self] Int:
    return self.first

    fn show_mutation():
    var somePair = Pair(5, 6)
    somePair.get_first_ref() = 1
    @value
    struct Pair:
    var first: Int
    var second: Int

    fn get_first_ref(inout self) -> ref [self] Int:
    return self.first

    fn show_mutation():
    var somePair = Pair(5, 6)
    somePair.get_first_ref() = 1

    This approach provides a general way to return an "automatically dereferenced" reference of a given type. Notably, this eliminates the need for __refitem__() to exist. __refitem__() has thus been removed and replaced with __getitem__() that returns a reference.

  • Mojo added support for infer-only parameters. Infer-only parameters must appear at the beginning of the parameter list and cannot be explicitly specified by the user. They are declared to the left of a // marker, much like positional-only parameters. This allows programmers to define functions with dependent parameters to be called without the caller specifying all the necessary parameters. For example:

    fn parameter_simd[dt: DType, //, value: Scalar[dt]]():
    print(value)

    fn call_it():
    parameter_simd[Int32(42)]()
    fn parameter_simd[dt: DType, //, value: Scalar[dt]]():
    print(value)

    fn call_it():
    parameter_simd[Int32(42)]()

    In the above example, Int32(42) is passed directly into value, the first parameter that isn't infer-only. dt is inferred from the parameter itself to be DType.int32.

    This also works with structs. For example:

    struct ScalarContainer[dt: DType, //, value: Scalar[dt]]:
    pass

    fn foo(x: ScalarContainer[Int32(0)]): # 'dt' is inferred as `DType.int32`
    pass
    struct ScalarContainer[dt: DType, //, value: Scalar[dt]]:
    pass

    fn foo(x: ScalarContainer[Int32(0)]): # 'dt' is inferred as `DType.int32`
    pass

    This should make working with dependent parameters more ergonomic. See Infer-only parameters in the Mojo Manual.

  • Mojo now allows functions overloaded on parameters to be resolved when forming references to, but not calling, those functions. For example, the following now works:

    fn overloaded_parameters[value: Int32]():
    pass

    fn overloaded_parameters[value: Float32]():
    pass

    fn form_reference():
    alias ref = overloaded_parameters[Int32()] # works!
    fn overloaded_parameters[value: Int32]():
    pass

    fn overloaded_parameters[value: Float32]():
    pass

    fn form_reference():
    alias ref = overloaded_parameters[Int32()] # works!
  • Mojo now supports adding a @deprecated decorator on structs, functions, traits, aliases, and global variables. The decorator marks the attached declaration as deprecated and causes a warning to be emitted when the deprecated declaration is referenced in user code. The decorator requires a deprecation message, specified as a string literal.

    @deprecated("Foo is deprecated, use Bar instead")
    struct Foo:
    pass

    fn outdated_api(x: Foo): # warning: Foo is deprecated, use Bar instead
    pass

    @deprecated("use another function!")
    fn bar():
    pass

    fn techdebt():
    bar() # warning: use another function!
    @deprecated("Foo is deprecated, use Bar instead")
    struct Foo:
    pass

    fn outdated_api(x: Foo): # warning: Foo is deprecated, use Bar instead
    pass

    @deprecated("use another function!")
    fn bar():
    pass

    fn techdebt():
    bar() # warning: use another function!
  • Mojo has introduced @parameter for, a new feature for compile-time programming. @parameter for defines a for loop where the sequence and the induction values in the sequence must be parameter values. For example:

    fn parameter_for[max: Int]():
    @parameter
    for i in range(max)
    @parameter
    if i == 10:
    print("found 10!")
    fn parameter_for[max: Int]():
    @parameter
    for i in range(max)
    @parameter
    if i == 10:
    print("found 10!")

    Currently, @parameter for requires the sequence's __iter__() method to return a _StridedRangeIterator, meaning the induction variables must be Int. The intention is to lift these restrictions in the future.

  • The is_mutable parameter of Reference and AnyLifetime is now a Bool, not a low-level __mlir_type.i1 value.

    This improves the ergonomics of spelling out a Reference type explicitly.

  • Mojo will now link to a Python dynamic library based on the Python on top of your search path: PATH. This enables you to activate a virtual environment like conda and have access to Python modules installed in that environment without setting MOJO_PYTHON_LIBRARY. Previously Mojo would find a libpython dynamic library on installation and put the path in .modular/modular.cfg, which could result in version conflicts if you activated a virtual environment of a different Python version.

  • AnyRegType has been renamed to AnyTrivialRegType and Mojo now forbids binding non-trivial register-passable types to AnyTrivialRegType. This closes a major safety hole in the language. Please use AnyType for generic code going forward.

  • The let keyword has been completely removed from the language. We previously removed let declarations but still provided an error message to users. Now, it is completely gone from the grammar.

Standard library changes

  • New traits and related features:

    • Added built-in repr() function and Representable trait. (PR #2361)

    • Added the Indexer trait to denote types that implement the __index__() method which allows these types to be accepted in common __getitem__() and __setitem__() implementations, as well as allow a new built-in index() function to be called on them. Most standard library containers can now be indexed by any type that implements Indexer. For example:

      @value
      struct AlwaysZero(Indexer):
      fn __index__(self) -> Int:
      return 0

      struct MyList:
      var data: List[Int]

      fn __init__(inout self):
      self.data = List[Int](1, 2, 3, 4)

      fn __getitem__[T: Indexer](self, idx: T) -> Int:
      return self.data[index(idx)]

      print(MyList()[AlwaysZero()]) # prints `1`
      @value
      struct AlwaysZero(Indexer):
      fn __index__(self) -> Int:
      return 0

      struct MyList:
      var data: List[Int]

      fn __init__(inout self):
      self.data = List[Int](1, 2, 3, 4)

      fn __getitem__[T: Indexer](self, idx: T) -> Int:
      return self.data[index(idx)]

      print(MyList()[AlwaysZero()]) # prints `1`

      Types conforming to the Indexer trait are implicitly convertible to Int. This means you can write generic APIs that take Int instead of making them take a generic type that conforms to Indexer. For example:

      @value
      struct AlwaysZero(Indexer):
      fn __index__(self) -> Int:
      return 0

      @value
      struct Incrementer:
      fn __getitem__(self, idx: Int) -> Int:
      return idx + 1

      var a = Incrementer()
      print(a[AlwaysZero()]) # works and prints 1
      @value
      struct AlwaysZero(Indexer):
      fn __index__(self) -> Int:
      return 0

      @value
      struct Incrementer:
      fn __getitem__(self, idx: Int) -> Int:
      return idx + 1

      var a = Incrementer()
      print(a[AlwaysZero()]) # works and prints 1

      (PR #2685)

    • Added traits allowing user-defined types to be supported by various built-in and math functions.

      FunctionTraitRequired method
      abs()Absable__abs__()
      pow()Powable__pow__()
      round()Roundable__round__()
      math.ceilmath.Ceilable__ceil__()
      math.ceildivmath.CeilDivable
      math.CeilDivableRaising
      __ceildiv__()
      math.floormath.Floorable__floor__()
      math.truncTruncable__trunc__()

      Notes:

      • Conforming to the Powable trait also means that the type can be used with the power operator (**).

      • For ceildiv(), structs can conform to either the CeilDivable trait or CeilDivableRaising trait.

      • Due to ongoing refactoring, the traits Ceilable, CeilDivable, Floorable, and Truncable do not appear in the API reference. They should be imported from the math module, except for Truncable which is (temporarily) available as a built-in trait and does not need to be imported.

      Example:

      from math import sqrt

      @value
      struct Complex2(Absable, Roundable):
      var re: Float64
      var im: Float64

      fn __abs__(self) -> Self:
      return Self(sqrt(self.re * self.re + self.im * self.im), 0.0)

      fn __round__(self) -> Self:
      return Self(round(self.re, 0), round(self.im, 0))

      fn __round__(self, ndigits: Int) -> Self:
      return Self(round(self.re, ndigits), round(self.im, ndigits))

      from math import sqrt

      @value
      struct Complex2(Absable, Roundable):
      var re: Float64
      var im: Float64

      fn __abs__(self) -> Self:
      return Self(sqrt(self.re * self.re + self.im * self.im), 0.0)

      fn __round__(self) -> Self:
      return Self(round(self.re, 0), round(self.im, 0))

      fn __round__(self, ndigits: Int) -> Self:
      return Self(round(self.re, ndigits), round(self.im, ndigits))

  • Benchmarking:

    • The bencher module as part of the benchmark package is now public and documented. This module provides types such as Bencher which provides the ability to execute a Benchmark and allows for benchmarking configuration via the BenchmarkConfig struct.
  • String and friends:

    • Breaking. Implicit conversion to String is now removed for builtin classes/types. Use str() explicitly to convert to String.

    • Added String.isspace() method conformant with Python's universal separators. This replaces the isspace() free function from the string module. (If you need the old function, it is temporarily available as _isspace(). It now takes a UInt8 but is otherwise unchanged.)

    • String.split() now defaults to whitespace and has Pythonic behavior in that it removes all adjacent whitespace by default.

    • String.strip(), lstrip() and rstrip() can now remove custom characters other than whitespace. In addition, there are now several useful aliases for whitespace, ASCII lower/uppercase, and so on. (PR #2555)

    • String now has a splitlines() method, which allows splitting strings at line boundaries. This method supports universal newlines and provides an option to retain or remove the line break characters. (PR #2810)

    • InlinedString has been renamed to InlineString to be consistent with other types.

    • StringRef now implements strip(), which can be used to remove leading and trailing whitespace. (PR #2683)

    • StringRef now implements startswith() and endswith(). (PR #2710)

    • Added a new StringSlice type, to replace uses of the unsafe StringRef type in standard library code.

      StringSlice is a non-owning reference to encoded string data. Unlike StringRef, a StringSlice is safely tied to the lifetime of the data it points to.

      • Added new as_string_slice() methods to String and StringLiteral.
      • Added StringSlice initializer from an UnsafePointer and a length in bytes.
    • Added a new as_bytes_slice() method to String and StringLiteral, which returns a Span of the bytes owned by the string.

    • Continued transition to UnsafePointer and unsigned byte type for strings:

      • Renamed String._as_ptr() to String.unsafe_ptr(), and changed return type to UnsafePointer (was DTypePointer).
      • Renamed StringLiteral.data() to StringLiteral.unsafe_ptr(), and changed return type to UnsafePointer (was DTypePointer).
      • InlineString.as_ptr() has been renamed to unsafe_ptr() and now returns an UnsafePointer[UInt8] (was DTypePointer[DType.int8]).
      • StringRef.data is now an UnsafePointer (was DTypePointer) and StringRef.unsafe_ptr() now returns an UnsafePointer[UInt8] (was DTypePointer[DType.int8]).
  • Other built-ins:

    • The Slice.__len__() function has been removed and Slice no longer conforms to the Sized trait. This clarifies the ambiguity of the semantics: the length of a slice always depends on the length of the object being sliced. Users that need the existing functionality can use the Slice.unsafe_indices() method. This makes it explicit that this implementation does not check if the slice bounds are concrete or within any given object's length.

    • Added a built-in sort() function for lists of elements that conform to the ComparableCollectionElement trait.(PR #2609)

    • int() can now take a string and a specified base to parse an integer from a string: int("ff", 16) returns 255. Additionally, if a base of zero is specified, the string will be parsed as if it was an integer literal, with the base determined by whether the string contains the prefix "0x", "0o", or "0b". (PR #2273, fixes #2274)

    • Added the bin() built-in function to convert integral types into their binary string representation. (PR #2603)

    • Added the atof() built-in function, which can convert a String to a float64. (PR #2649)

    • You can now use the built-in any() and all() functions to check for truthy elements in a collection. Because SIMD.__bool__() is now constrained to size=1, You must explicitly use these to get the truthy value of a SIMD vector with more than one element. This avoids common bugs around implicit conversion of SIMD to Bool. (PR #2600)

      For example:

        fn truthy_simd():
      var vec = SIMD[DType.int32, 4](0, 1, 2, 3)
      if any(vec):
      print("any elements are truthy")
      if all(vec):
      print("all elements are truthy")
        fn truthy_simd():
      var vec = SIMD[DType.int32, 4](0, 1, 2, 3)
      if any(vec):
      print("any elements are truthy")
      if all(vec):
      print("all elements are truthy")
    • object now implements all the bitwise operators. (PR #2324)

    • Tuple now supports __contains__(). (PR #2709) For example:

      var x = Tuple(1, 2, True)
      if 1 in x:
      print("x contains 1")
      var x = Tuple(1, 2, True)
      if 1 in x:
      print("x contains 1")
    • ListLiteral and Tuple now only require that element types be Movable. Consequently, ListLiteral and Tuple are themselves no longer Copyable.

    • Added new ImmutableStaticLifetime and MutableStaticLifetime helpers.

  • UnsafePointer and others:

    • Added new memcpy() overload for UnsafePointer[Scalar[_]] pointers.

    • Removed the get_null() method from UnsafePointer and other pointer types. Please use the default constructor instead: UnsafePointer[T]().

    • Many functions returning a pointer type have been unified to have a public API function of unsafe_ptr().

    • The Tensor.data() method has been renamed to unsafe_ptr(). The return type is still a DTypePointer[T].

  • Collections:

    • List now has an index() method that allows you to find the (first) location of an element in a List of EqualityComparable types. For example:

      var my_list = List[Int](2, 3, 5, 7, 3)
      print(my_list.index(3)) # prints 1
      var my_list = List[Int](2, 3, 5, 7, 3)
      print(my_list.index(3)) # prints 1
    • List can now be converted to a String with a simplified syntax:

      var my_list = List[Int](2, 3)
      print(my_list.__str__()) # prints [2, 3]
      var my_list = List[Int](2, 3)
      print(my_list.__str__()) # prints [2, 3]

      Note that List doesn't conform to the Stringable trait yet so you cannot use str(my_list) yet. (PR #2673)

    • List has a simplified syntax to call the count() method: my_list.count(x). (PR #2675)

    • List() now supports __contains__(), so you can now use lists with the in operator:

      if x in my_list:
      if x in my_list:

      (PR #2667)

    • List now has an unsafe_get() to get the reference to an element without bounds check or wraparound for negative indices. Note that this method is unsafe. Use with caution. PR #2800)

    • Added a fromkeys() method to Dict to return a Dict with the specified keys and values. (PR 2622)

    • Added a clear() method to Dict. (PR 2627)

    • Dict now supports reversed() for its items() and values() iterators. (PR #2340)

    • Dict now has a simplified conversion to String with my_dict.__str__(). Note that Dict does not conform to the Stringable trait so str(my_dict) is not possible yet. (PR #2674)

    • Dict now implements get(key) and get(key, default) functions. (PR #2519)

    • Added a temporary __get_ref(key) method to Dict, allowing you to get a Reference to a dictionary value.

    • Added a new InlineList type, a stack-allocated list with a static maximum size. (PR 2587#) (PR #2703)

    • Added a new Span type for taking slices of contiguous collections. (PR #2595)

  • os module:

  • SIMD type:

  • math package:

    • The math.bit module has been moved to a new top-level bit module. The following functions in this module have been renamed:

      • ctlz -> countl_zero
      • cttz -> countr_zero
      • bit_length -> bit_width
      • ctpop -> pop_count
      • bswap -> byte_swap
      • bitreverse -> bit_reverse
    • The math.rotate_bits_left() and math.rotate_bits_right() functions have been moved to the bit module.

    • The is_power_of_2() function in the math module is now called is_power_of_two() and located in the bit module.

    • The abs(), round(), min(), max(), pow(), and divmod() functions have moved from math to builtin, so you no longer need to import these functions.

    • The math.tgamma() function has been renamed to math.gamma() to conform with Python's naming.

    • The implementation of the following functions have been moved from the math module to the new utils.numerics module: isfinite(), isinf(), isnan(), nan(), nextafter(), and ulp(). The functions continue to be exposed in the math module.

    • math.gcd() now works on negative inputs, and like Python's implementation, accepts a variadic list of integers. New overloads for a List or Spanof integers are also added. (PR #2777)

  • Async and coroutines:

    • Coroutine now requires a lifetime parameter. This parameter is set automatically by the parser when calling an async function. It contains the lifetimes of all the arguments and any lifetime accesses by the arguments. This ensures that argument captures by async functions keep the arguments alive as long as the coroutine is alive.

    • Async function calls are no longer allowed to borrow non-trivial register-passable types. Because async functions capture their arguments but register-passable types don't have lifetimes (yet), Mojo is not able to correctly track the reference, making this unsafe. To cover this safety gap, Mojo has temporarily disallowed binding non-trivial register-passable types to borrowed arguments in async functions.

  • Miscellaneous:

    • Added an InlineArray type that works on memory-only types. Compare with the existing StaticTuple type, which is conceptually an array type, but only works on AnyTrivialRegType. (PR #2294)

    • The base64 package now includes encoding and decoding support for both the Base64 and Base16 encoding schemes. (PR #2364) (PR #2584)

    • The take() function in Variant and Optional has been renamed to unsafe_take().

    • The get() function in Variant has been replaced by __getitem__(). That is, v.get[T]() should be replaced with v[T].

    • Various functions in the algorithm module are now built-in functions. This includes sort(), swap(), and partition(). swap() and partition() will likely shuffle around as we're reworking our built-in sort() function and optimizing it.

  • infinity and NaN are now correctly handled in testing.assert_almost_equal() and an inf function has been added to utils/numerics.mojo. (PR #2375)

Tooling changes

  • Invoking mojo package my-package -o my-dir on the command line, where my-package is a Mojo package source directory, and my-dir is an existing directory, now outputs a Mojo package to my-dir/my-package.mojopkg. Previously, this had to be spelled out, as in -o my-dir/my-package.mojopkg.

  • The Mojo Language Server now reports a warning when a local variable is unused.

  • Several mojo subcommands now support a --diagnostic-format option that changes the format with which errors, warnings, and other diagnostics are printed. By specifying --diagnostic-format json on the command line, errors and other diagnostics will be output in a structured JSON Lines format that is easier for machines to parse.

    The full list of subcommands that support --diagnostic-format is as follows: mojo build, mojo doc, mojo run, mojo package, and mojo test. Further, the mojo test --json option has been subsumed into this new option; for the same behavior, run mojo test --diagnostic-format json.

    Note that the format of the JSON output may change; we don't currently guarantee its stability across releases of Mojo.

  • A new --validate-doc-strings option has been added to mojo to emit errors on invalid doc strings instead of warnings.

  • The --warn-missing-doc-strings flag for mojo has been renamed to --diagnose-missing-doc-strings.

  • A new decorator, @doc_private, was added that can be used to hide a declaration from being generated in the output of mojo doc. It also removes the requirement that the declaration has documentation (for example, when used with --diagnose-missing-doc-strings).

  • Debugger users can now set breakpoints on function calls in O0 builds even if the call has been inlined by the compiler.

  • The Mojo Language Server now supports renaming local variables.

Other changes

❌ Removed

  • The @unroll decorator has been deprecated and removed. The decorator was supposed to guarantee that a decorated loop would be unrolled, or else the compiler would error. In practice, this guarantee was eroded over time, as a compiler-based approach cannot be as robust as the Mojo parameter system. In addition, the @unroll decorator did not make the loop induction variables parameter values, limiting its usefulness. Please see @parameter for for a replacement!

  • The method object.print() has been removed. Since object now conforms to the Stringable trait, you can use print(my_object) instead.

  • The following functions have been removed from the math module:

    • clamp(); use the new SIMD.clamp() method instead.
    • round_half_down() and round_half_up(); these can be trivially implemented using the ceil() and floor() functions.
    • add(), sub(), mul(), div(), mod(), greater(), greater_equal(), less(), less_equal(), equal(), not_equal(), logical_and(), logical_xor(), and logical_not(); Instead, users should rely directly on the corresponding operators (+, -, *, /, %, >, >=, <, <=, ==, !=, &, ^, and ~).
    • identity() and reciprocal(); users can implement these trivially.
    • select(); removed in favor of using SIMD.select() directly.
    • is_even() and is_odd(); these can be trivially implemented using bitwise & with 1.
    • roundeven(); the new SIMD.roundeven() method now provides the identical functionality.
    • div_ceil(); use the new ceildiv() function.
    • rotate_left() and rotate_right(); the same functionality is available in the builtin SIMD.rotate_{left,right}() methods for SIMD types, and the bit.rotate_bits_{left,right})() methods for Int.
    • An overload of math.pow() taking an integer parameter exponent.
    • align_down_residual(); it can be trivially implemented using align_down().
    • all_true(), any_true(), and none_true(); use SIMD.reduce_and() and SIMD.reduce_or() directly.
    • reduce_bit_count(); use the new SIMD.reduce_bit_count() directly.
    • rint() and nearbyint(); use round() or SIMD.roundeven() as appropriate.
  • The EvaluationMethod has been removed from math.polynomial and Estrin's method is no longer available. This method was limited to degree 10 or less, underutilized, and its performance unclear. In the future, this might be reintroduced with an improved implementation if needed, when better performance benchmarking infrastructure is available. The default behavior of math.polynomial.polynomial_evaluate() is unchanged (Horner's method).

  • The math.bit.select() and math.bit.bit_and() functions have been removed. The same functionality is available in the builtin SIMD.select and SIMD.__and__() methods, respectively.

  • The math.limit module has been removed. The same functionality is available as follows:

    • math.limit.inf(): use utils.numerics.max_or_inf()
    • math.limit.neginf(): use utils.numerics.min_or_neg_inf()
    • math.limit.max_finite(): use utils.numerics.max_finite()
    • math.limit.min_finite(): use utils.numerics.min_finite()
  • The tensor.random module has been removed. The same functionality is now accessible via the Tensor.rand() and Tensor.randn() static methods.

  • The builtin SIMD struct no longer conforms to Indexer; users must explicitly cast Scalar values using int.

🛠️ Fixed

  • #1837 Fix self-referential variant crashing the compiler.
  • #2363 Fix LSP crashing on simple trait definitions.
  • #1787 Fix error when using // on FloatLiteral in alias expression.
  • Made several improvements to dictionary performance. Dicts with integer keys are most heavily affected, but large dicts and dicts with large values will also see large improvements.
  • #2692 Fix assert_raises to include calling location.

Special thanks

Special thanks to our community contributors:

@rd4com, @toiletsandpaper, @helehex, @artemiogr97, @mikowals, @kernhanda, @lsh, @LJ-9801, @YichengDWu, @gabrieldemarmiesse, @fknfilewalker, @jayzhan211, @martinvuyk, @ChristopherLR, @mzaks, @bgreni, @Brian-M-J, @leandrolcampos

v24.3 (2024-05-02)

✨ Highlights

  • AnyPointer was renamed to UnsafePointer and is now Mojo's preferred unsafe pointer type. It has several enhancements, including:

    • The element type can now be any type: it doesn't require Movable.

    • Because of this, the take_value(), emplace_value(), and move_into() methods have been changed to top-level functions and renamed. The new functions are:

    • A new destroy_pointee() function runs the destructor on the pointee.

    • UnsafePointer can be initialized directly from a Reference with UnsafePointer(someRef) and can convert to a reference with yourPointer[]. Both infer element type and address space. Note that when you convert a pointer to a reference, there's no way for Mojo to track the lifetime of the original value. So the resulting reference is no safer than the original pointer.

  • All of the pointer types received some cleanup to make them more consistent, for example the unsafe.bitcast() global function is now a consistent bitcast() method on the pointers, which can convert element type and address space.

  • Improvements to variadic arguments support.

    • Heterogeneous variadic pack arguments now work reliably even with memory types, and have a more convenient API to use, as defined by the VariadicPack type. For example, a simplified version of print can be implemented like this:

      fn print[T: Stringable, *Ts: Stringable](first: T, *rest: *Ts):
      print_string(str(first))

      @parameter
      fn print_elt[T: Stringable](a: T):
      print_string(" ")
      print_string(a)
      rest.each[print_elt]()
      fn print[T: Stringable, *Ts: Stringable](first: T, *rest: *Ts):
      print_string(str(first))

      @parameter
      fn print_elt[T: Stringable](a: T):
      print_string(" ")
      print_string(a)
      rest.each[print_elt]()
    • Mojo now supports declaring functions that have both optional and variadic arguments, both positional and keyword-only. For example, this now works:

      fn variadic_arg_after_default(
      a: Int, b: Int = 3, *args: Int, c: Int, d: Int = 1, **kwargs: Int
      ): ...
      fn variadic_arg_after_default(
      a: Int, b: Int = 3, *args: Int, c: Int, d: Int = 1, **kwargs: Int
      ): ...

      Positional variadic parameters also work in the presence of optional parameters. That is:

      fn variadic_param_after_default[e: Int, f: Int = 2, *params: Int]():
      pass
      fn variadic_param_after_default[e: Int, f: Int = 2, *params: Int]():
      pass

      Note that variadic keyword parameters are not supported yet.

    For more information, see Variadic arguments in the Mojo Manual.

  • The mojo build and mojo run commands now support a -g option. This shorter alias is equivalent to writing --debug-level full. This option is also available in the mojo debug command, but is already the default.

  • Many new standard library APIs have been filled in, including many community contributions. Changes are listed in the standard library section.

  • The Mojo Manual has a new page on Types.

Language changes

  • Certain dunder methods that take indices (__getitem__(), __setitem__(), and __refitem__()) or names (__getattr__() and __setattr__()) can now take the index or name as a parameter value instead of an argument value. This is enabled when you define one of these methods with no argument other than self (for a getter) or self and the set value (for a setter).

    This enables types that can only be subscripted into with parameters, as well as things like the following example, which passes the attribute name as a parameter so that attribute names can be checked at compile time.

    struct RGB:
    fn __getattr__[name: StringLiteral](self) -> Int:
    @parameter
    if name == "r": return ...
    elif name == "g": return ...
    else:
    constrained[name == "b", "can only access with r, g, or b members"]()
    return ...

    var rgb = RGB()
    print(rgb.b) # Works
    print(rgb.q) # Compile error
    struct RGB:
    fn __getattr__[name: StringLiteral](self) -> Int:
    @parameter
    if name == "r": return ...
    elif name == "g": return ...
    else:
    constrained[name == "b", "can only access with r, g, or b members"]()
    return ...

    var rgb = RGB()
    print(rgb.b) # Works
    print(rgb.q) # Compile error
  • Mojo now allows users to capture the source location of code and call location of functions dynamically using the __source_location() and __call_location() functions. For example:

    from builtin._location import __call_location

    @always_inline
    fn my_assert(cond: Bool, msg: String):
    if not cond:
    var call_loc = __call_location()
    print("In", call_loc.file_name, "on line", str(call_loc.line) + ":", msg)

    fn main():
    my_assert(False, "always fails") # some_file.mojo, line 193
    from builtin._location import __call_location

    @always_inline
    fn my_assert(cond: Bool, msg: String):
    if not cond:
    var call_loc = __call_location()
    print("In", call_loc.file_name, "on line", str(call_loc.line) + ":", msg)

    fn main():
    my_assert(False, "always fails") # some_file.mojo, line 193

    This prints "In /path/to/some_file.mojo on line 193: always fails". Note that __call_location() only works in @always_inline or @always_inline("nodebug") functions. It gives incorrect results if placed in an @always_inline function that's called from an @always_inline("nodebug") function.

    This feature is still evolving and for the time being you need to explicitly import these APIs, as shown above. In the future, these will probably be built-in functions and not require an import statement.

    Neither __source_location() nor __call_location() work when called in a parameter context. For example:

    from builtin._location import __call_location

    @always_inline
    fn mystery_location() -> String:
    var loc = __call_location()
    return str(loc.file_name)

    def main():
    alias doesnt_work = mystery_location() # <unknown location in parameter context>
    from builtin._location import __call_location

    @always_inline
    fn mystery_location() -> String:
    var loc = __call_location()
    return str(loc.file_name)

    def main():
    alias doesnt_work = mystery_location() # <unknown location in parameter context>

Standard library changes

⭐️ New

  • List has several new methods:

    • pop(index) for removing an element at a particular index. By default, List.pop() removes the last element in the list. (@LJ-9801, fixes #2017)

    • resize(new_size) for resizing the list without the need to specify an additional value. (@mikowals, fixes #2133)

    • insert(index, value) for inserting a value at a specified index into the List. (@whym1here, fixes #2134)

    • A new constructor List(ptr, size, capacity) to to avoid needing to do a deep copy of an existing contiguous memory allocation when constructing a new List. (@StandinKP, fixes #2170)

  • Dict now has a update() method to update keys/values from another Dict. (@gabrieldemarmiesse)

  • Set now has named methods for set operations:

    • difference() mapping to -
    • difference_update() mapping to -=
    • intersection_update() mapping to &=
    • update() mapping to |=

    (@arvindavoudi)

  • Dict, List, and Set all conform to the Boolable trait. The collections evaluate to True if they contain any elements, False otherwise:

    def list_names(names: List[String]):
    if names:
    for name in names:
    print(name[])
    else:
    print("No names to list.")
    def list_names(names: List[String]):
    if names:
    for name in names:
    print(name[])
    else:
    print("No names to list.")

    (@gabrieldemarmiesse)

  • Added reversed() function for creating reversed iterators. Several range types, List, and Dict now support iterating in reverse.

    var numbers = List(1, 2, 3, 4, 5)
    for number in reversed(numbers):
    print(number)
    var numbers = List(1, 2, 3, 4, 5)
    for number in reversed(numbers):
    print(number)

    (@helehex and @jayzhan211, contributes towards #2325)

  • Optional now implements __is__ and __isnot__ methods so that you can compare an Optional with None. For example:

    var opt = Optional(1)
    if opt is not None:
    print(opt.value()[])
    var opt = Optional(1)
    if opt is not None:
    print(opt.value()[])

    (@gabrieldemarmiesse)

  • Tuple now works with memory-only element types like String and allows you to directly index into it with a parameter expression. This means you can now simply use x = tup[1] like Python instead of x = tup.get[1, Int](). You can also assign into tuple elements now as well with tup[1] = x.

    var tuple = ("Green", 9.3)
    var name = tuple[0]
    var value = tuple[1]
    var tuple = ("Green", 9.3)
    var name = tuple[0]
    var value = tuple[1]

    Note that because the subscript must be a parameter expression, you can't iterate through a Tuple using an ordinary for loop.

  • The Reference type has several changes, including:

    • It has moved to the memory.reference module instead of memory.unsafe.

    • Reference now has an unsafe_bitcast() method, similar to the pointer types.

    • Several unsafe methods were removed, including offset(), destroy_element_unsafe() and emplace_ref_unsafe(). This is because Reference is a safe type—use UnsafePointer to do unsafe operations.

  • Bool can now be implicitly converted from any type conforming to the Boolable trait. This means that you no longer need to write code like this:

    @value
    struct MyBoolable:
    fn __bool__(self) -> Bool: ...

    fn takes_boolable[T: Boolable](cond: T): ...

    takes_boolable(MyBoolable())
    @value
    struct MyBoolable:
    fn __bool__(self) -> Bool: ...

    fn takes_boolable[T: Boolable](cond: T): ...

    takes_boolable(MyBoolable())

    Instead, you can simply write:

    fn takes_bool(cond: Bool): ...

    takes_bool(MyBoolable())
    fn takes_bool(cond: Bool): ...

    takes_bool(MyBoolable())

    Note that calls to takes_bool() will perform the implicit conversion, so in some cases is it still better to explicitly declare a type parameter, e.g.:

    fn takes_two_boolables[T: Boolable](a: T, b: T):
    # Short circuit means `b.__bool__()` might not be evaluated.
    if a.__bool__() and b.__bool__():
    ...
    fn takes_two_boolables[T: Boolable](a: T, b: T):
    # Short circuit means `b.__bool__()` might not be evaluated.
    if a.__bool__() and b.__bool__():
    ...
  • PythonObject now conforms to the KeyElement trait, meaning that it can be used as key type for Dict. This allows you to easily build and interact with Python dictionaries in Mojo:

    def main():
    d = PythonObject(Dict[PythonObject, PythonObject]())
    d["foo"] = 12
    d[7] = "bar"
    d["foo"] = [1, 2, "something else"]
    print(d) # prints `{'foo': [1, 2, 'something else'], 7: 'bar'}`
    def main():
    d = PythonObject(Dict[PythonObject, PythonObject]())
    d["foo"] = 12
    d[7] = "bar"
    d["foo"] = [1, 2, "something else"]
    print(d) # prints `{'foo': [1, 2, 'something else'], 7: 'bar'}`
  • FileHandle.seek() now has a whence argument that defaults to os.SEEK_SET to seek from the beginning of the file. You can now set to os.SEEK_CUR to offset by the current FileHandle seek position:

    var f = open("/tmp/example.txt")
    # Skip 32 bytes
    f.seek(os.SEEK_CUR, 32)
    var f = open("/tmp/example.txt")
    # Skip 32 bytes
    f.seek(os.SEEK_CUR, 32)

    Or os.SEEK_END to offset from the end of file:

    # Start from 32 bytes before the end of the file
    f.seek(os.SEEK_END, -32)
    # Start from 32 bytes before the end of the file
    f.seek(os.SEEK_END, -32)
  • FileHandle.read() can now read straight into a DTypePointer:

    var file = open("/tmp/example.txt", "r")

    # Allocate and load 8 elements
    var ptr = DTypePointer[DType.float32].alloc(8)
    var bytes = file.read(ptr, 8)
    print("bytes read", bytes)
    print(ptr.load[width=8]())
    var file = open("/tmp/example.txt", "r")

    # Allocate and load 8 elements
    var ptr = DTypePointer[DType.float32].alloc(8)
    var bytes = file.read(ptr, 8)
    print("bytes read", bytes)
    print(ptr.load[width=8]())
  • The sys module now contains an exit() function that would exit a Mojo program with the specified error code.

    from sys import exit

    exit(0)
    from sys import exit

    exit(0)
  • The constructors for Tensor have been changed to be more consistent. As a result, constructors take the shape as the first argument (instead of the second) when constructing a tensor with pointer data.

    If you pass a single scalar value to the Tensor constructor, it now broadcasts the value to all elements in the tensor. For example, Tensor[DType.float32](TensorShape(2,2), 0) constructs a 2x2 tensor initialized with all zeros. This provides an easy way to fill in the data of a tensor.

  • String now has removeprefix() and removesuffix() methods. (@gabrieldemarmiesse)

  • The ord and chr functions have been improved to accept any Unicode character. (@mzaks, contributes towards #1616)

  • atol() now handles whitespace. The atol()function is used internally by String.__int__(), so int(String( " 10 ")) now returns 10 instead of raising an error. (@artemiogr97)

  • SIMD now implements the __rmod__() method. (@bgreni, fixes #1482)

  • bool(None) is now implemented. (@zhoujingya)

  • The DTypePointer type now implements gather() for gathering a SIMD vector from offsets of a current pointer. Similarly, support for scatter() was added to scatter a SIMD vector into offsets of the current pointer. (@leandrolcampos)

  • The len() function now handles a range() specified with a negative end value, so that things like len(range(-1)) work correctly. (@soraros)

  • debug_assert() now prints its location (filename, line, and column where it was called) in its error message. Similarly, the assert helpers in the testing module now include location information in their messages.

  • The testing.assert_equal[SIMD]() function now raises if any of the elements mismatch in the two SIMD arguments being compared. (@gabrieldemarmiesse)

  • The testing.assert_almost_equal() and math.isclose() functions now have an equal_nan flag. When set to True, then NaNs are considered equal.

  • The object type now supports the division, modulo, and left and right shift operators, including the in-place and reverse variants. (@LJ-9801, fixes #2224)

  • Added checked arithmetic operations for SIMD integers.

    SIMD integer types (including the sized integer scalars like Int64) can now perform checked additions, subtractions, and multiplications using the following new methods:

    • add_with_overflow()
    • sub_with_overflow()
    • mul_with_overflow()

    Checked arithmetic allows the caller to determine if an operation exceeded the numeric limits of the type. For example:

    var simd = SIMD[DType.int8, 4](7, 11, 13, 17)
    var product: SIMD[DType.int8, 4]
    var overflow: SIMD[DType.bool, 4]
    (product, overflow) = simd.mul_with_overflow(simd)
    for i in range(len(product)):
    if overflow[i]:
    print("<overflow>")
    else:
    print(product[i])
    var simd = SIMD[DType.int8, 4](7, 11, 13, 17)
    var product: SIMD[DType.int8, 4]
    var overflow: SIMD[DType.bool, 4]
    (product, overflow) = simd.mul_with_overflow(simd)
    for i in range(len(product)):
    if overflow[i]:
    print("<overflow>")
    else:
    print(product[i])

    (@lsh)

  • Added os.remove() and os.unlink() for deleting files. (@artemiogr97, fixes #2306)

🦋 Changed

Tooling changes

  • The behavior of mojo build when invoked without an output -o argument has changed slightly: mojo build ./test-dir/program.mojo now outputs an executable to the path ./program, whereas before it would output to the path ./test-dir/program.

  • The mojo package command no longer supports the -D flag. All compilation environment flags should be provided at the point of package use (e.g. mojo run or mojo build).

  • The REPL no longer allows type level variable declarations to be uninitialized, e.g. it will reject var s: String. This is because it does not do proper lifetime tracking (yet!) across cells, and so such code would lead to a crash. You can work around this by initializing to a dummy value and overwriting later. This limitation only applies to top level variables, variables in functions work as they always have.

Other changes

Low-level language changes

  • A low-level __get_mvalue_as_litref(x) builtin was added to give access to the underlying memory representation as a !lit.ref value without checking initialization status of the underlying value. This is useful in very low-level logic but isn't designed for general usability and will likely change in the future.

  • Properties can now be specified on inline MLIR ops:

    _ = __mlir_op.`kgen.source_loc`[
    _type = (
    __mlir_type.index, __mlir_type.index, __mlir_type.`!kgen.string`
    ),
    _properties = __mlir_attr.`{inlineCount = 1 : i64}`,
    ]()
    _ = __mlir_op.`kgen.source_loc`[
    _type = (
    __mlir_type.index, __mlir_type.index, __mlir_type.`!kgen.string`
    ),
    _properties = __mlir_attr.`{inlineCount = 1 : i64}`,
    ]()

    As the example shows above, the protected _properties attribute can be passed during op construction, with an MLIR DictionaryAttr value.

❌ Removed

  • Support for "register only" variadic packs has been removed. Instead of AnyRegType, please upgrade your code to AnyType in examples like this:

    fn your_function[*Types: AnyRegType](*args: *Ts): ...
    fn your_function[*Types: AnyRegType](*args: *Ts): ...

    This move gives you access to a nicer API and has the benefit of being memory safe and correct for non-trivial types. If you need specific APIs on the types, please use the correct trait instead of AnyType.

  • List.pop_back() has been removed. Use List.pop() instead which defaults to popping the last element in the list.

  • SIMD.to_int(value) has been removed. Use int(value) instead.

  • The __get_lvalue_as_address(x) magic function has been removed. To get a reference to a value use Reference(x) and if you need an unsafe pointer, you can use UnsafePointer.address_of(x).

🛠️ Fixed

  • #516 and #1817 and many others, e.g. "Can't create a function that returns two strings."

  • #1178 (os/kern) failure (5).

  • #1609 alias with DynamicVector[Tuple[Int]] fails.

  • #1987 Defining main in a Mojo package is an error, for now. This is not intended to work yet, erroring for now will help to prevent accidental undefined behavior.

  • #1215 and #1949 The Mojo LSP server no longer cuts off hover previews for functions with functional arguments, parameters, or results.

  • #1901 Fixed Mojo LSP and documentation generation handling of inout arguments.

  • #1913 - 0__ no longer crashes the Mojo parser.

  • #1924 JIT debugging on Mac has been fixed.

  • #1941 Mojo variadic arguments don't work with non-trivial register-only types.

  • #1963 a!=0 is now parsed and formatted correctly by mojo format.

  • #1676 Fix a crash related to @value decorator and structs with empty body.

  • #1917 Fix a crash after syntax error during tuple creation.

  • #2006 The Mojo LSP now properly supports signature types with named arguments and parameters.

  • #2007 and #1997 The Mojo LSP no longer crashes on certain types of closures.

  • #1675 Ensure @value decorator fails gracefully after duplicate field error.

  • #2068 Fix SIMD.reduce() for size_out == 2. (@soraros)

v24.2.1 (2024-04-11)

This release doesn't include any changes to Mojo.

v24.2 (2024-03-28)

🔥 Legendary

  • The Mojo standard library is now open source! Check out the README for everything you need to get started.

  • Structs and other nominal types are now allowed to implicitly conform to traits. A struct implicitly conforms to a trait if it implements all the requirements for the trait. For example, any struct that implements the __str__() method implicitly conforms to Stringable, and is usable with the str() built-in function.

    @value
    struct Foo:
    fn __str__(self) -> String:
    return "foo!"

    fn main():
    print(str(Foo())) # prints 'foo!'
    @value
    struct Foo:
    fn __str__(self) -> String:
    return "foo!"

    fn main():
    print(str(Foo())) # prints 'foo!'

    We still strongly encourage you to explicitly list the traits a struct conforms to when possible:

    @value
    struct Foo(Stringable): ...
    @value
    struct Foo(Stringable): ...

    Not only is this useful for documentation and for communicating intentions, but in the future, explicit conformance will be useful for features like default methods and extensions.

  • Mojo's Python interoperability now supports passing keyword arguments to Python functions:

    from python import Python

    def main():
    plt = Python.import_module("matplotlib.pyplot")
    plt.plot((5, 10), (10, 15), color="red")
    plt.show()
    from python import Python

    def main():
    plt = Python.import_module("matplotlib.pyplot")
    plt.plot((5, 10), (10, 15), color="red")
    plt.show()

Language changes

⭐️ New

  • Mojo now has support for variadic keyword arguments, often referred to as **kwargs. This means you can now declare and call functions like this:

    fn print_nicely(**kwargs: Int) raises:
    for key in kwargs.keys():
    print(key[], "=", kwargs[key[]])

    # prints:
    # `a = 7`
    # `y = 8`
    print_nicely(a=7, y=8)
    fn print_nicely(**kwargs: Int) raises:
    for key in kwargs.keys():
    print(key[], "=", kwargs[key[]])

    # prints:
    # `a = 7`
    # `y = 8`
    print_nicely(a=7, y=8)

    For more details (and a list of current limitations), see Variadic keyword arguments in the Mojo manual.

🦋 Changed or removed

  • let declarations now produce a compile time error instead of a warning, our next step in removing let declarations. The compiler still recognizes the let keyword for now in order to produce a good error message, but that will be removed in subsequent releases.

  • Mojo now warns about unused values in both def and fn declarations, instead of completely disabling the warning in defs. It never warns about unused object or PythonObject values, tying the warning to these types instead of the kind of function they are unused in. This will help catch API usage bugs in defs and make imported Python APIs more ergonomic in fns.

  • For the time being, dynamic type values will be disabled in the language. For example, the following will now fail with an error:

    var t = Int  # dynamic type values not allowed

    struct SomeType: ...

    takes_type(SomeType) # dynamic type values not allowed
    var t = Int  # dynamic type values not allowed

    struct SomeType: ...

    takes_type(SomeType) # dynamic type values not allowed

    We want to take a step back and (re)design type valued variables, existentials, and other dynamic features. This does not affect type valued parameters, so the following works as before:

    alias t = Int  # still 🔥

    struct SomeType: ...

    takes_type[SomeType]() # already 🔥

    >fn uses_trait[T: SomeTrait](value: T): ... # still 🔥
    alias t = Int  # still 🔥

    struct SomeType: ...

    takes_type[SomeType]() # already 🔥

    >fn uses_trait[T: SomeTrait](value: T): ... # still 🔥
  • The *_ expression in parameter expressions is now required to occur at the end of a positional parameter list, instead of being allowed in the middle.

    # No longer supported
    alias FirstUnbound = SomeStruct[*_, 42]
    alias MidUnbound = SomeStruct[7, *_, 6]
    # Still supported
    alias LastUnbound = SomeStruct[42, *_]
    # No longer supported
    alias FirstUnbound = SomeStruct[*_, 42]
    alias MidUnbound = SomeStruct[7, *_, 6]
    # Still supported
    alias LastUnbound = SomeStruct[42, *_]

    We narrowed this because we want to encourage type designers to get the order of parameters right, and want to extend *_ to support keyword parameters as well in the future.

Standard library changes

⭐️ New

  • DynamicVector has been renamed to List, and has moved from the collections.vector module to the collections.list module. In addition:

    • You can now construct a List from a variadic number of values. For example:

      var numbers = List[Int](1, 2, 3)
      var numbers = List[Int](1, 2, 3)
    • List and InlinedFixedVector types now support negative indexing. This means that you can write vec[-1] which is equivalent to vec[len(vec)-1].

    • List.push_back() has been removed. Please use the append() function instead.

  • The print() function now takes sep and end keyword arguments. This means that you can write:

    print("Hello", "Mojo", sep=", ", end="!!!\n") # prints Hello, Mojo!!!
    print("Hello", "Mojo", sep=", ", end="!!!\n") # prints Hello, Mojo!!!

    sep defaults to the empty string and end defaults to "\n".

    Also, the print_no_newline() function has been removed. Please use print(end="") instead.

  • The FloatLiteral type is now an infinite-precision nonmaterializable type. This means you can do compile-time calculations using FloatLiteral without rounding errors. When materialized at runtime, a FloatLiteral value is converted to a Float64.

    # third is an infinite-precision FloatLiteral value
    alias third = 1.0 / 3.0
    # t is a Float64
    var t = third
    # third is an infinite-precision FloatLiteral value
    alias third = 1.0 / 3.0
    # t is a Float64
    var t = third
  • String types all conform to the IntableRaising trait. This means that you can now call int("123") to get the integer 123. If the integer cannot be parsed from the string, then an error is raised.

  • The Tensor type now has argmax() and argmin() functions to compute the position of the max or min value. Note: this should return a Tensor[Int] but currently the output tensor is the same type as the input tensor. This will be fixed in a future release.

  • Added a new collections.OptionalReg type, a register-passable alternative to Optional.

  • The ulp() function has been added to the math module. This allows you to get the units of least precision (or units of last place) of a floating point value.

🦋 Changed

  • The simd_load(), simd_store(), aligned_simd_load(), and aligned_simd_store() methods on DTypePointer, Buffer, and NDBuffer have been merged into a more expressive set of load() and store() methods with keyword-only width and alignment parameters:

    # Doesn't work
    my_simd = my_buffer.simd_load[simd_width](index)
    # Works
    my_simd = my_buffer.load[width=simd_width](index)
    # Doesn't work
    my_buffer.aligned_simd_store[width, alignment](my_simd)
    # Works
    my_buffer.store[width=width, alignment=alignment](my_simd)
    # Doesn't work
    my_simd = my_buffer.simd_load[simd_width](index)
    # Works
    my_simd = my_buffer.load[width=simd_width](index)
    # Doesn't work
    my_buffer.aligned_simd_store[width, alignment](my_simd)
    # Works
    my_buffer.store[width=width, alignment=alignment](my_simd)
  • The EqualityComparable trait now requires the __ne__() method for conformance in addition to the previously required __eq__() method.

  • Many types now declare conformance to EqualityComparable trait.

  • StaticTuple parameter order has changed to StaticTuple[type, size] for consistency with SIMD and similar collection types.

  • The signature of the elementwise() function has been changed. The new order is is function, simd_width, and then rank. As a result, the rank parameter can now be inferred and one can call elementwise() without it:

    elementwise[func, simd_width](shape)
    elementwise[func, simd_width](shape)
  • PythonObject is now register-passable.

  • PythonObject.__iter__() now works correctly on more types of iterable Python objects. Attempting to iterate over non-iterable objects will now raise an exception instead of behaving as if iterating over an empty sequence. __iter__() also now borrows self rather than requiring inout, allowing code like:

    for value in my_dict.values():
    ...
    for value in my_dict.values():
    ...

🚚 Moved

  • We took the opportunity to rehome some modules into their correct package as we were going through the process of open-sourcing the Mojo standard library. Specifically, the following are some breaking changes worth calling out. Please update your import statements accordingly.

    • Buffer, NDBuffer, and friends have moved from the memory package into a new buffer package.

      from buffer import Buffer, NDBuffer
      from buffer import Buffer, NDBuffer
    • utils.list, including the Dim and DimList types, has moved to the buffer package.

      from buffer import Dim, DimList
      from buffer import Dim, DimList
    • The parallel_memcpy() function has moved from the memory package into the buffer package.

      from buffer import parallel_memcpy
      from buffer import parallel_memcpy
    • The rand() and randn() functions from the random package that return a Tensor have moved to the tensor package. Note that the overloads that write to a DTypePointer remain in the random package.

      If you happen to be using both versions in the same source file, you can import them both using the import as syntax:

      from tensor import rand
      from random import rand as rand_dt
      from tensor import rand
      from random import rand as rand_dt
    • The trap() function has been renamed to abort(). It also has moved from the debug module to the os module.

      from os import abort
      from os import abort
    • The isinf() and isfinite() methods have been moved from math.limits to the math module.

      from math import ininf, isfinite
      from math import ininf, isfinite

Tooling changes

⭐️ New

  • Docstring code blocks can now use %# to hide lines of code from documentation generation.

    For example:

    var value = 5
    %# print(value)
    var value = 5
    %# print(value)

    Will generate documentation of the form:

    var value = 5
    var value = 5

    Hidden lines are processed as if they were normal code lines during test execution. This allows for writing additional code within a docstring example that is only used to ensure the example is runnable/testable.

  • The Mojo LSP server now allow you to specify additional search paths to use when resolving imported modules in a document. You can specify search paths on the command line, using the -I option, or you can add them to the mojo.lsp.includeDirs setting in the VS Code extension.

Other changes

❌ Removed

  • The __get_address_as_lvalue magic function has been removed. You can now get an LValue from a Pointer or Reference by using the dereference operator ([]):

    var ptr: Pointer[MyRecord]
    ...
    # Doesn't work
    __get_address_as_lvalue(ptr.value) = MyRecord(3, 5)
    # Works
    ptr[] = MyRecord(3, 5)
    var ptr: Pointer[MyRecord]
    ...
    # Doesn't work
    __get_address_as_lvalue(ptr.value) = MyRecord(3, 5)
    # Works
    ptr[] = MyRecord(3, 5)
  • The type parameter for the memcpy function is now automatically inferred. This means that calls to memcpy of the form memcpy[Dtype.xyz](...) will no longer work and the user would have to change the code to memcpy(...).

  • The memcpy() overload that worked on Buffer types has been removed in favor of just overloads for Pointer and DTypePointer:

    # Doesn't work
    memcpy(destBuffer, srcBuffer, count)
    # Works
    memcpy(destBuffer.data, srcBuffer.data, count)
    # Doesn't work
    memcpy(destBuffer, srcBuffer, count)
    # Works
    memcpy(destBuffer.data, srcBuffer.data, count)
  • The functions max_or_inf(), min_or_neginf() have been removed from math.limit. These functions were only used by the SIMD type.

  • As mentioned previously, the print_no_newline() function has been removed. Please use print(end="") instead.

🛠️ Fixed

  • #1362 - Parameter inference now recursively matches function types.
  • #951 - Functions that were both async and @always_inline incorrectly errored.
  • #1858 - Trait with parametric methods regression.
  • #1892 - Forbid unsupported decorators on traits.
  • #1735 - Trait-typed values are incorrectly considered equal.
  • #1909 - Crash due to nested import in unreachable block.
  • #1921 - Parser crashes binding Reference to lvalue with subtype lifetime.
  • #1945 - Optional[T].or_else() should return T instead of Optional[T].
  • #1940 - Constrain math.copysign to floating point or integral types.
  • #1838 - Variadic print does not work when specifying end=""
  • #1826 - The SIMD.reduce methods correctly handle edge cases where size_out >= size.

v24.1.1 (2024-03-18)

This release includes installer improvements and enhanced error reporting for installation issues. Otherwise it is functionally identical to Mojo 24.1.

v24.1 (2024-02-29)

🔥 Legendary

  • Mojo is now bundled with the MAX platform!

    As such, the Mojo package version now matches the MAX version, which follows a YY.MAJOR.MINOR version scheme. Because this is our first release in 2024, that makes this version 24.1.

  • Mojo debugging support is here! The Mojo VS Code extension includes debugger support. For details, see Debugging in the Mojo Manual.

⭐️ New

  • We now have a Set type in our collections! Set is backed by a Dict, so it has fast add, remove, and in checks, and requires member elements to conform to the KeyElement trait.

    from collections import Set

    var set = Set[Int](1, 2, 3)
    print(len(set)) # 3
    set.add(4)

    for element in set:
    print(element[])

    set -= Set[Int](3, 4, 5)
    print(set == Set[Int](1, 2)) # True
    print(set | Set[Int](0, 1) == Set[Int](0, 1, 2)) # True
    let element = set.pop()
    print(len(set)) # 1
    from collections import Set

    var set = Set[Int](1, 2, 3)
    print(len(set)) # 3
    set.add(4)

    for element in set:
    print(element[])

    set -= Set[Int](3, 4, 5)
    print(set == Set[Int](1, 2)) # True
    print(set | Set[Int](0, 1) == Set[Int](0, 1, 2)) # True
    let element = set.pop()
    print(len(set)) # 1
  • Mojo now supports the x in y expression as syntax sugar for y.__contains__(x) as well as x not in y.

  • Mojo now has support for keyword-only arguments and parameters. For example:

    fn my_product(a: Int, b: Int = 1, *, c: Int, d: Int = 2):
    print(a * b * c * d)

    my_product(3, c=5) # prints '30'
    my_product(3, 5, d=7) # error: missing 1 required keyword-only argument: 'c'
    fn my_product(a: Int, b: Int = 1, *, c: Int, d: Int = 2):
    print(a * b * c * d)

    my_product(3, c=5) # prints '30'
    my_product(3, 5, d=7) # error: missing 1 required keyword-only argument: 'c'

    This includes support for declaring signatures that use both variadic and keyword-only arguments/parameters. For example, the following is now possible:

    fn prod_with_offset(*args: Int, offset: Int = 0) -> Int:
    var res = 1
    for i in range(len(args)):
    res *= args[i]
    return res + offset

    print(prod_with_offset(2, 3, 4, 10)) # prints 240
    print(prod_with_offset(2, 3, 4, offset=10)) # prints 34
    fn prod_with_offset(*args: Int, offset: Int = 0) -> Int:
    var res = 1
    for i in range(len(args)):
    res *= args[i]
    return res + offset

    print(prod_with_offset(2, 3, 4, 10)) # prints 240
    print(prod_with_offset(2, 3, 4, offset=10)) # prints 34

    Note that variadic keyword-only arguments/parameters (for example, **kwargs) are not supported yet. That is, the following is not allowed:

    fn variadic_kw_only(a: Int, **kwargs): ...
    fn variadic_kw_only(a: Int, **kwargs): ...

    For more information, see Positional-only and keyword-only arguments in the Mojo Manual.

  • The print() function now accepts a keyword-only argument for the end which is useful for controlling whether a newline is printed or not after printing the elements. By default, end defaults to "\n" as before.

  • The Mojo SDK can now be installed on AWS Graviton instances.

  • A new version of the Mojo Playground is available. The new playground is a simple interactive editor for Mojo code, similar to the Rust Playground or Go Playground. The old JupyterLab based playground will remain online until March 20th.

  • The Mojo LSP server will now generate fixits for populating empty documentation strings:

    fn foo(arg: Int):
    """""" # Unexpected empty documentation string
    fn foo(arg: Int):
    """""" # Unexpected empty documentation string

    Applying the fixit from above will generate:

    fn foo(arg: Int):
    """[summary].

    Args:
    arg: [description].
    """
    fn foo(arg: Int):
    """[summary].

    Args:
    arg: [description].
    """
  • Added new *_ syntax that allows users to explicitly unbind any number of positional parameters. For example:

    struct StructWithDefault[a: Int, b: Int, c: Int = 8, d: Int = 9]: pass

    alias all_unbound = StructWithDefault[*_]
    # equivalent to
    alias all_unbound = StructWithDefault[_, _, _, _]

    alias first_bound = StructWithDefault[5, *_]
    # equivalent to
    alias first_bound = StructWithDefault[5, _, _, _]

    alias last_bound = StructWithDefault[*_, 6]
    # equivalent to
    alias last_bound = StructWithDefault[_, _, _, 6]

    alias mid_unbound = StructWithDefault[3, *_, 4]
    # equivalent to
    alias mid_unbound = StructWithDefault[3, _, _, 4]
    struct StructWithDefault[a: Int, b: Int, c: Int = 8, d: Int = 9]: pass

    alias all_unbound = StructWithDefault[*_]
    # equivalent to
    alias all_unbound = StructWithDefault[_, _, _, _]

    alias first_bound = StructWithDefault[5, *_]
    # equivalent to
    alias first_bound = StructWithDefault[5, _, _, _]

    alias last_bound = StructWithDefault[*_, 6]
    # equivalent to
    alias last_bound = StructWithDefault[_, _, _, 6]

    alias mid_unbound = StructWithDefault[3, *_, 4]
    # equivalent to
    alias mid_unbound = StructWithDefault[3, _, _, 4]

    As demonstrated above, this syntax can be used to explicitly unbind an arbitrary number of parameters, at the beginning, at the end, or in the middle of the operand list. Since these unbound parameters must be explicitly specified at some point, default values for these parameters are not applied. For example:

    alias last_bound = StructWithDefault[*_, 6]
    # When using last_bound, you must specify a, b, and c. last_bound
    # doesn't have a default value for `c`.
    var s = last_bound[1, 2, 3]()
    alias last_bound = StructWithDefault[*_, 6]
    # When using last_bound, you must specify a, b, and c. last_bound
    # doesn't have a default value for `c`.
    var s = last_bound[1, 2, 3]()

    For more information see the Mojo Manual sections on partially-bound types and automatic parameterization of functions.

  • DynamicVector now supports iteration. Iteration values are instances of Reference and require dereferencing:

    var v: DynamicVector[String]()
    v.append("Alice")
    v.append("Bob")
    v.append("Charlie")
    for x in v:
    x[] = str("Hello, ") + x[]
    for x in v:
    print(x[])
    var v: DynamicVector[String]()
    v.append("Alice")
    v.append("Bob")
    v.append("Charlie")
    for x in v:
    x[] = str("Hello, ") + x[]
    for x in v:
    print(x[])
  • DynamicVector now has reverse() and extend() methods.

  • The mojo package command now produces compilation agnostic packages. Compilation options such as O0, or --debug-level, are no longer needed or accepted. As a result, packages are now smaller, and extremely portable.

  • Initializers for @register_passable values can (and should!) now be specified with inout self arguments just like memory-only types:

    @register_passable
    struct YourPair:
    var a: Int
    var b: Int
    fn __init__(inout self):
    self.a = 42
    self.b = 17
    fn __copyinit__(inout self, existing: Self):
    self.a = existing.a
    self.b = existing.b
    @register_passable
    struct YourPair:
    var a: Int
    var b: Int
    fn __init__(inout self):
    self.a = 42
    self.b = 17
    fn __copyinit__(inout self, existing: Self):
    self.a = existing.a
    self.b = existing.b

    This form makes the language more consistent, more similar to Python, and easier to implement advanced features for. There is also no performance impact of using this new form: the compiler arranges to automatically return the value in a register without requiring you to worry about it.

    The older -> Self syntax is still supported in this release, but will be removed in a subsequent one, so please migrate your code. One thing to watch out for: a given struct should use one style or the other, mixing some of each won't work well.

  • The inout self initializer form is required for initializers of @register_passable types that may raise errors:

    @register_passable
    struct RaisingCtor:
    fn __init__(inout self) raises:
    raise
    @register_passable
    struct RaisingCtor:
    fn __init__(inout self) raises:
    raise
  • async functions that may raise errors have been temporarily disabled in this build. The implementation of Mojo async is undergoing a rework 🚧.

  • The standard library slice type has been renamed to Slice, and a slice function has been introduced. This makes Mojo closer to Python and makes the Slice type follow the naming conventions of other types like Int.

  • "Slice" syntax in subscripts is no longer hard coded to the builtin slice type: it now works with any type accepted by a container's __getitem__() method. For example:

    @value
    struct UnusualSlice:
    var a: Int
    var b: Float64
    var c: String

    struct YourContainer:
    fn __getitem__(self, slice: UnusualSlice) -> T: ...
    @value
    struct UnusualSlice:
    var a: Int
    var b: Float64
    var c: String

    struct YourContainer:
    fn __getitem__(self, slice: UnusualSlice) -> T: ...

    Given this implementation, you can subscript into an instance of YourContainer like yc[42:3.14:"🔥"] and the three values are passed to the UnusualSlice constructor.

  • T