Skip to main content

April 2023

Week of 2023-04-24​

  • πŸ“’ The default behavior of nested functions has been changed. Mojo nested functions that capture are by default are non-parametric, runtime closures, meaning that:

    def foo(x):
        # This:
        def bar(y): return x * y
        # Is the same as:
        let bar = lambda y: x * y

    These closures cannot have input or result parameters, because they are always materialized as runtime values. Values captured in the closure (x in the above example), are captured by copy: values with copy constructors cannot be copied and captures are immutable in the closure.

    Nested functions that don't capture anything are by default "parametric" closures: they can have parameters and they can be used as parameter values. To restore the previous behavior for capturing closures, "parametric, capture-by-unsafe-reference closures", tag the nested function with the @parameter decorator.

  • πŸ“’ Mojo now has full support for "runtime" closures: nested functions that capture state materialized as runtime values. This includes taking the address of functions, indirect calls, and passing closures around through function arguments. Note that capture-by-reference is still unsafe!

    You can also take references to member functions with instances of that class using foo.member_function, which creates a closure with foo bound to the self argument.

  • πŸ“’ Mojo now supports Python style with statements and context managers.

    These things are very helpful for implementing things like our trace region support and things like Runtime support.

    A context manager in Mojo implements three methods:

    fn __enter__(self) -> T:
    fn __exit__(self):
    fn __exit__(self, err: Error) -> Bool:

    The first is invoked when the context is entered, and returns a value that may optionally be bound to a target for use in the with body. If the with block exits normally, the second method is invoked to clean it up. If an error is raised, the third method is invoked with the Error value. If that method returns true, the error is considered handled, if it returns false, the error is re-thrown so propagation continues out of the 'with' block.

  • πŸ“’ Mojo functions now support variable scopes! Explicit var and let declarations inside functions can shadow declarations from higher "scopes", where a scope is defined as any new indentation block. In addition, the for loop iteration variable is now scoped to the loop body, so it is finally possible to write

    for i in range(1): pass
    for i in range(2): pass
  • πŸ“’ Mojo now supports an @value decorator on structs to reduce boilerplate and encourage best practices in value semantics. The @value decorator looks to see the struct has a fieldwise initializer (which has arguments for each field of the struct), a __copyinit__ method, and a __moveinit__ method, and synthesizes the missing ones if possible. For example, if you write:

    @value
    struct MyPet:
      var name: String
      var age: Int

    The @value decorator will synthesize the following members for you:

    fn __init__(inout self, owned name: String, age: Int):
        self.name = name^
        self.age = age
    fn __copyinit__(inout self, existing: Self):
        self.name = existing.name
        self.age = existing.age
    fn __moveinit__(inout self, owned existing: Self):
        self.name = existing.name^
        self.age = existing.age

    This decorator can greatly reduce the boilerplate needed to define common aggregates, and gives you best practices in ownership management automatically. The @value decorator can be used with types that need custom copy constructors (your definition wins). We can explore having the decorator take arguments to further customize its behavior in the future.

  • πŸ“š Memcpy and memcmp now consistently use count as the byte count.

  • πŸ“š Add a variadic string join on strings.

  • πŸ“š Introduce a reduce_bit_count method to count the number of 1 across all elements in a SIMD vector.

  • πŸ“š Optimize the pow function if the exponent is integral.

  • πŸ“š Add a len function which dispatches to __len__ across the different structs that support it.

Week of 2023-04-17​

  • πŸ“’ Error messages have been significantly improved, thanks to prettier printing for Mojo types in diagnostics.

  • πŸ“’ Variadic values can now be indexed directly without wrapping them in a VariadicList!

  • πŸ“’ let declarations in a function can now be lazily initialized, and var declarations that are never mutated get a warning suggesting they be converted to a let declaration. Lazy initialization allows more flexible patterns of initialization than requiring the initializer be inline, e.g.:

    let x: Int
    if cond:
        x = foo()
    else:
        x = bar()
    use(x)
  • πŸ“’ Functions defined with def now return object by default, instead of None. This means you can return values (convertible to object) inside def functions without specifying a return type.

  • πŸ“’ The @raises decorator has been removed. Raising fn should be declared by specifying raises after the function argument list. The rationale is that raises is part of the type system, instead of a function modifier.

  • πŸ“’ The BoolLiteral type has been removed. Mojo now emits True and False directly as Bool.

  • πŸ“’ Syntax for function types has been added. You can now write function types with fn(Int) -> String or async def(&String, *Int) -> None. No more writing !kgen.signature types by hand!

  • πŸ“’ Float literals are not emitted as FloatLiteral instead of an MLIR f64 type!

  • πŸ“’ Automatic destructors are now supported by Mojo types, currently spelled fn __del___(owned self): (the extra underscore will be dropped shortly). These destructors work like Python object destructors and similar to C++ destructors, with the major difference being that they run "as soon as possible" after the last use of a value. This means they are not suitable for use in C++-style RAII patterns (use the with statement for that, which is currently unsupported).

    These should be generally reliable for both memory-only and register-passable types, with the caveat that closures are known to not capture values correctly. Be very careful with interesting types in the vicinity of a closure!

  • A new (extremely dangerous!) builtin function is available for low-level ownership muckery. The __get_address_as_owned_value(x) builtin takes a low-level address value (of !kgen.pointer type) and returns an owned value for the memory that is pointed to. This value is assumed live at the invocation of the builtin, but is "owned" so it needs to be consumed by the caller, otherwise it will be automatically destroyed. This is an effective way to do a "placement delete" on a pointer.

    # "Placement delete": destroy the initialized object begin pointed to.
    _ = __get_address_as_owned_value(somePointer.value)
    
    # Result value can be consumed by anything that takes it as an 'owned'
    # argument as well.
    consume(__get_address_as_owned_value(somePointer.value))
  • Another magic operator, named __get_address_as_uninit_lvalue(x) joins the magic LValue operator family. This operator projects a pointer to an LValue like __get_address_as_lvalue(x). The difference is that __get_address_as_uninit_lvalue(x) tells the compiler that the pointee is uninitialized on entry and initialized on exit, which means that you can use it as a "placement new" in C++ sense. __get_address_as_lvalue(x) tells the compiler that the pointee is initialized already, so reassigning over it will run the destructor.

    # "*Re*placement new": destroy the existing SomeHeavy value in the memory,
    # then initialize a new value into the slot.
    __get_address_as_lvalue(somePointer.value) = SomeHeavy(4, 5)
    
    # Ok to use an lvalue, convert to borrow etc.
    use(__get_address_as_lvalue(somePointer.value))
    
    # "Placement new": Initialize a new value into uninitialied memory.
    __get_address_as_uninit_lvalue(somePointer.value) = SomeHeavy(4, 5)
    
    # Error, cannot read from uninitialized memory.
    use(__get_address_as_uninit_lvalue(somePointer.value))

    Note that __get_address_as_lvalue assumes that there is already a value at the specified address, so the assignment above will run the SomeHeavy destructor (if any) before reassigning over the value.

  • πŸ“’ Implement full support for __moveinit__ (aka move constructors)

    This implements the ability for memory-only types to define two different types of move ctors if they'd like:

    1. fn __moveinit__(inout self, owned existing: Self): Traditional Rust style moving constructors that shuffles data around while taking ownership of the source binding.
    2. fn __moveinit__(inout self, inout existing: Self):: C++ style "stealing" move constructors that can be used to take from an arbitrary LValue.

    This gives us great expressive capability (better than Rust/C++/Swift) and composes naturally into our lifetime tracking and value categorization system.

  • The __call__ method of a callable type has been relaxed to take self by borrow, allow non-copyable callees to be called.

  • Implicit conversions are now invoked in raise statements properly, allowing converting strings to Error type.

  • Automatic destructors are turned on for __del__ instead of __del___.

  • πŸ“š Add the builtin FloatLiteral type.

  • πŸ“š Add integral floordiv and mod for the SIMD type that handle negative values.

  • πŸ“š Add an F64 to String converter.

  • πŸ“š Make the print function take variadic inputs.

Week of 2023-04-10​

  • πŸ“’ Introduce consume operator x^

    This introduces the postfix consume operator, which produces an RValue given a lifetime tracked object (and, someday, a movable LValue).

  • Mojo now automatically synthesizes empty destructor methods for certain types when needed.

  • The object type has been built out into a fully-dynamic type, with dynamic function dispatch, with full error handling support.

    def foo(a) -> object:
        return (a + 3.45) < [1, 2, 3] # raises a TypeError
  • πŸ“’ The @always_inline decorator is no longer required for passing capturing closures as parameters, for both the functions themselves as functions with capturing closures in their parameters. These functions are still inlined but it is an implementation detail of capturing parameter closures. Mojo now distinguishes between capturing and non-capturing closures. Nested functions are capturing by default and can be made non-capturing with the @noncapturing decorator. A top-level function can be passed as a capturing closure by marking it with the @closure decorator.

  • πŸ“’ Support for list literals has been added. List literals [1, 2, 3] generate a variadic heterogeneous list type.

  • Variadics have been extended to work with memory-primary types.

  • Slice syntax is now fully-supported with a new builtin slice object, added to the compiler builtins. Slice indexing with a[1:2:3] now emits calls to __setitem__ and __getitem__ with a slice object.

  • Call syntax has been wired up to __call__. You can now f() on custom types!

  • Closures are now explicitly typed as capturing or non-capturing. If a function intends to accept a capturing closure, it must specify the capturing function effect.

  • πŸ“š Add a Tile2D function to enable generic 2D tiling optimizations.

  • πŸ“š Add the slice struct to enable getting/setting spans of elements via getitem/setitem.

  • πŸ“š Add syntax sugar to autotuning for both specifying the autotuned values, searching, and declaring the evaluation function.

Week of 2023-04-03​

  • The AnyType and NoneType aliases were added and auto-imported in all files.

  • πŸ“’ The Mojo VS Code extension has been improved with docstring validation. It will now warn when a function's docstring has a wrong argument name, for example.

  • πŸ“’ A new built-in literal type TupleLiteral was added in _CompilerBuiltin. It represents literal tuple values such as (1, 2.0) or ().

  • πŸ“’ The Int type has been moved to a new Builtin module and is auto-imported in all code. The type of integer literals has been changed from the MLIR index type to the Int type.

  • Mojo now has a powerful flow-sensitive uninitialized variable checker. This means that you need to initialize values before using them, even if you overwrite all subcomponents. This enables the compiler to reason about the true lifetime of values, which is an important stepping stone to getting automatic value destruction in place.

  • πŸ“’ Call syntax support has been added. Now you can directly call an object that implements the __call__ method, like foo(5).

  • πŸ“’ The name for copy constructors got renamed from __copy__ to __copyinit__. Furthermore, non-@register_passable types now implement it like they do an init method where you fill in a by-reference self, for example:

    fn __copyinit__(inout self, existing: Self):
        self.first = existing.first
        self.second = existing.second

    This makes copy construction work more similarly to initialization, and still keeps copies x = y distinct from initialization x = T(y).

  • πŸ“’ Initializers for memory-primary types are now required to be in the form __init__(inout self, ...): with a None result type, but for register primary types, it remains in the form __init__(...) -> Self:. The T{} initializer syntax has been removed for memory-primary types.

  • Mojo String literals now emit a builtin StringLiteral type! One less MLIR type to worry about.

  • New __getattr__ and __setattr__ dunder methods were added. Mojo calls these methods on a type when attempting member lookup of a non-static member. This allows writing dynamic objects like x.foo() where foo is not a member of x.

  • Early destructor support has been added. Types can now define a special destructor method __del___ (note three underscores). This is an early feature and it is still being built out. There are many caveats, bugs, and missing pieces. Stay tuned!

  • πŸ“š Integer division and mod have been corrected for rounding in the presence of negative numbers.

  • πŸ“š Add scalar types (UI8, SI32, F32, F64, etc.) which are aliases to SIMD[1, type].

Was this page helpful?