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+1a, 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()
Mojo functions can return an auto-dereferenced refeference to storage with a new ref keyword in the result type specifier. For example:
@valuestruct Pair: var first: Int var second: Int fn get_first_ref(inout self) -> ref[__lifetime_of(self)] Int: return self.firstfn 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)]()
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]]: passfn 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](): passfn overloaded_parameters[value: Float32](): passfn 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: passfn outdated_api(x: Foo): # warning: Foo is deprecated, use Bar instead pass@deprecated("use another function!")fn bar(): passfn 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!")
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.
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.
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.