Skip to main content
Version: 1.0

v0.6.0 (2023-12-04)

🔥 Legendary

  • Traits have arrived!

    You can now define a trait, which consists of a required set of method prototypes. A struct can conform to the trait by implementing these methods. This lets you write generic functions that work on any structs that conform to a given trait.

    The following section gives a brief overview of traits—see the Mojo Manual and this traits blog post for more details!

    Traits are declared with the trait keyword. The bodies of traits should contain method signatures declared with ... as their bodies. Default method implementations are not supported yet.

    trait SomeTrait:
    fn required_method(self, x: Int): ...

    The trait can be implemented on a struct by inheriting from it.

    struct SomeStruct(SomeTrait):
    fn required_method(self, x: Int):
    print("hello traits", x)

    You can then write a generic functions that accepts any type that conforms to the trait. You do this by creating a parameterized function with a trait-typed parameter:

    fn fun_with_traits[T: SomeTrait](x: T):
    x.required_method(42)

    Which can be invoked with instances of types that conform to the trait:

    var thing = SomeStruct()
    # Infer the parameter `T`!
    fun_with_traits(thing)

    Traits can also inherit from other traits, which simply requires that implementers of the child trait also conform to all parent traits.

    trait Parent:
    fn parent_func(self): ...

    trait Child(Parent):
    fn child_func(self): ...

    Then, both child and parent trait methods can be invoked on instances of the trait Child. As well, an instance of the child trait can be converted to an instance of the parent trait.

    fn the_parents[T: Parent](x: T):
    x.parent_func()

    fn the_children[T: Child](x: T):
    x.child_func()
    x.parent_func()
    # Upcast `x` from instance of `Child` to `Parent`.
    the_parents(x)

    For more information, see the Traits page in the Mojo Manual.

  • A fundamental Destructable trait has been added to the language. This is a core trait that every trait automatically conforms to. This enables destruction of generic types and generic collections.

    Note: We're aware that this trait might be better spelled Destructible. We're planning on removing it in the future and moving its functionality to AnyType so that any type that doesn't provide its own destructor will have a default, no-op destructor.

  • We've added some traits to the standard library, you can implement these on your own types:

  • We added built-in len(), str(), and int() functions, which work with types that implement the Sized, Stringable, and Intable traits, respectively.

  • DynamicVector is now a proper generic collection that can use any type that implements the Movable and Copyable traits. This means you can now write, for example, DynamicVector[String]. Also, DynamicVector now invokes its element destructors upon destruction, so _del_old has been deleted.

  • print now works on any types that implement Stringable by invoking their __str__ method:

    @value
    struct BoxedInt(Stringable):
    var value: Int

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

    print(BoxedInt(11), "hello traits!", BoxedInt(42))

⭐️ New

  • The Mojo Manual is an all-new, complete Mojo user guide. It doesn't include everything about Mojo yet, but it includes a lot, and more than the original programming manual (now deprecated).

    Plus, the entire Mojo Manual and other Mojo docs are now open-sourced on GitHub, and we'd love to accept contributions to help us improve them!

  • Mojo now supports partial automatic parameterization: when a function is declared with an argument of a partially bound type, the unbound parameters of that type are implicitly added to the function's input parameters. For example:

    @value
    struct Fudge[a: Int, b: Int, c: Int = 7]: ...

    # These function declarations are roughly equivalent:
    fn eat(f: Fudge[5]): ... # implicitly parameterized
    fn eat[_b: Int](f: Fudge[5, _b]): ... # explicitly parameterized

    In the first signature for eat(), the b parameter isn't bound, so it's implicitly added as an input parameter on the function.

    In the second signature for eat(), the author has explicitly defined an input parameter (_b), which is bound to the second parameter on the argument type (which happens to be b).

    Both functions can be called like this:

    eat(Fudge[5, 8]())

    Mojo infers the value of the b parameter from the argument (in this case, 8).

    With the second signature, you can also pass the _b parameter value explicitly:

    eat[3](Fudge[5, 3]())

    Moreover, Mojo now allows you to explicitly mark parameters as unbound using the _ as syntax meaning "placeholder for an unbound parameter." For example:

    # These function declarations are roughly equivalent:
    fn eat(f: Fudge[5, _, c=_]): ... # implicitly parameterized
    fn eat(f: Fudge[c=_, a=5, b=_]): ... # implicitly parameterized
    fn eat[_b: Int, _c: Int](f: Fudge[5, _b, _c]): ... # explicitly parameterized

    The first two signatures explicitly unbind the b and c parameters.

    In the last signature, the _b and _c parameters are explicitly declared by the author, and bound to the b and c parameters in the argument type.

    Any of these signatures can be called like this:

    eat(Fudge[5, 8]())
    eat(Fudge[5, 8, 9]())

    Note that the default parameter values of struct parameters are bound, unless explicitly unbound by the user.

    For more information, see the Mojo Manual.

  • Parametric types can now be partially bound in certain contexts. For example, a new Scalar type alias has been added defined as:

    alias Scalar = SIMD[size=1]

    Which creates a parametric type alias Scalar with a single parameter of type DType. Types can also be partially or fully bound in other contexts. For instance, alias declarations of type values inside functions now work properly:

    fn type_aliases():
    alias T = SIMD
    print(T[DType.float32, 1]())
    alias Partial = T[type=DType.int32]
    print(Partial[2]())
  • The __mlir_op feature now supports operations that return multiple results. To use them, you write the _type field as a Tuple of types. For example:

    # The `ret` variable has type `Tuple[Int, Int]`.
    let ret = __mlir_op.`multi_result_op`[_type=(Int, Int)]()
  • Mojo now has the ability to read raw bytes from a file using the read_bytes() method. For example:

    with open("file.binary", "r") as f:
    data = f.read_bytes()
  • A size argument was added to the read() and read_bytes() methods on the builtin file.FileHandle. The size argument defaults to -1 and maintains the previous "read to EOF" behavior when size is negative.

    with open("file.binary", "r") as f:
    data1 = f.read_bytes(1024)
    data2 = f.read_bytes(256)
  • Path now has read_bytes() and read_text() methods to read file contents from a path:

    let text_path = Path("file.txt")
    let text = text_path.read_text()

    let binary_path = Path("file.binary")
    let data = binary_path.read_bytes()
  • Tensor has new save() and load() methods to save and load to file. These methods preserve shape and datatype information. For example:

    let tensor = Tensor[DType.float32]()
    tensor.save(path)

    let tensor_from_file = Tensor[DType.float32].load(path)
  • Subscripting added to DTypePointer and Pointer:

    let p = DTypePointer[DType.float16].alloc(4)
    for i in range(4):
    p[i] = i
    print(p[i])
  • file.FileHandle now has a seek() method.

  • String now has an rfind() method analogous to Python's str.rfind().

  • String now has an split() method analogous to Python's str.split().

  • Path now has a suffix() method analogous to Python's pathlib.Path.suffix.

  • The Mojo REPL now supports indented expressions, making it a bit easier to execute expressions copied from an indented block (such as a doc string).

  • The Mojo Language Server now implements the Document Symbols request. IDEs use this to provide support for Outline View and Go to Symbol. This addresses Issue #960.

  • The Mojo Language Server now shows documentation when code completing modules or packages in import statements.

  • The Mojo Language Server now supports processing code examples, defined as markdown Mojo code blocks, inside of doc strings. This enables IDE features while writing examples in API documentation.

  • The Mojo Language Server now provides semantic token information, providing better highlighting for symbols whose semantics are not statically analyzable.

  • The Mojo Language Server now classifies doc strings as folding ranges, making them easier to collapse, reducing vertical space while editing.

  • Command line options for the mojo driver that take arguments can now be written in either of two ways: both --foo FOO and --foo=FOO. Previously, only the former was valid.

🦋 Changed

  • Variadic list types VariadicList and VariadicListMem are now iterable. Variadic arguments are automatically projected into one of these types inside the function body, so var args can be iterated:

    fn print_ints(*nums: Int):
    for num in nums:
    print(num)
    print(len(nums))
  • The assert functions in the testing package now raise an Error when the assertion fails instead of returning a Bool for whether the assertion succeeded or not.

  • Parameters of AnyType type are no longer (implicitly) assumed to be register-passable. A new AnyRegType type is used to represent generic types that are register passable.

  • Changing the units in a benchmark report is now an argument instead of a parameter:

    let report = benchmark.run[timer]()
    report.print(Unit.ms)
  • Default values on inout arguments are no longer permitted, i.e. the following will now raise an error:

    fn inout_default(inout x: Int = 2): ...
  • The to_string() function has been removed from PythonObject in favor of the new __str__() function. This composes better with traits so it can be used with the generic str() function.

🛠️ Fixed

  • #734 - Consumption of struct works only for types with a __del__ method.

  • #910 - Parser crash when using memory-only generic type as return of function that raises.

  • #1060 - Mojo happily parses code that has messed up indentation

  • #1159 - The language server doesn't warn about bad return type.

  • #1166 - warning: unreachable code after return statement with context manager

  • #1098 - The language server doesn't highlight properties of PythonObjects correctly.

  • #1153 - The language server crashes when parsing an invalid multi-nested module import.

  • #1236 - The language server doesn't show autocomplete in if statements.

  • #1246 - Warning diagnostics are transient in the presence of caching.

Known Issue

  • There is an issue affecting Jupyter notebooks that use autotuning and traits. This issue only manifests on macOS, and the same code runs without issue outside of the notebooks. This issue affects the Matrix multiplication in Mojo notebook.