Skip to main content
Version: 1.0

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)

    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())

    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.
  • 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"

    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): ...

    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
  • 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 __TypeOfAllTypes. 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!"

    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"

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

    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 + "!")

      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) + ")"

      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, ")")

      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("{} {} {}").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'"
    • 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]]

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

      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]]):

      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)

      Becomes:

      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 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)

      Also allowing more convenient call syntax:

      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
    • 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)
    • 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
    • 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)
    • 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')
  • 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)

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

    • 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
    • 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
  • 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