v24.4 (2024-06-07)
✨ Highlights
Big themes for this release:
-
Improvements to the performance and ease-of-use for
deffunctions. -
Continued unification of standard library APIs around the
UnsafePointertype. -
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 formechanism for expressing compile-time loops, which replaces the earlier (and less reliable)@unrolldecorator. -
New Mojo Manual pages on Control flow, Testing and using unsafe pointers.
Language changes
-
Mojo has changed how
deffunction arguments are processed. Previously, by default, arguments to adefwere treated according to theownedconvention, 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
borrowedexplicitly. Now Mojo takes a different approach:deffunctions take arguments asborrowedby default (consistent withfnfunctions) 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
deffunction are more flexible: you can now implicitly declare variables as the result of a tuple return, usinga,b,c = foo(). For example: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() -
Mojo functions can return an auto-dereferenced reference to storage with a new
refkeyword 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() = 1This 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 intovalue, the first parameter that isn't infer-only.dtis inferred from the parameter itself to beDType.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`
passThis 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! -
Mojo now supports adding a
@deprecateddecorator 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! -
Mojo has introduced
@parameter for, a new feature for compile-time programming.@parameter fordefines 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 forrequires the sequence's__iter__()method to return a_StridedRangeIterator, meaning the induction variables must beInt. The intention is to lift these restrictions in the future. -
The
is_mutableparameter ofReferenceandAnyLifetimeis now aBool, not a low-level__mlir_type.i1value.This improves the ergonomics of spelling out a
Referencetype 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 likecondaand have access to Python modules installed in that environment without settingMOJO_PYTHON_LIBRARY. Previously Mojo would find alibpythondynamic 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. -
AnyRegTypehas been renamed to__TypeOfAllTypesand Mojo now forbids binding non-trivial register-passable types to__TypeOfAllTypes. This closes a major safety hole in the language. Please useAnyTypefor generic code going forward. -
The
letkeyword has been completely removed from the language. We previously removedletdeclarations 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 andRepresentabletrait. (PR #2361) -
Added the
Indexertrait 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-inindex()function to be called on them. Most standard library containers can now be indexed by any type that implementsIndexer. 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`Types conforming to the
Indexertrait are implicitly convertible to Int. This means you can write generic APIs that takeIntinstead of making them take a generic type that conforms toIndexer. 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(PR #2685)
-
Added traits allowing user-defined types to be supported by various built-in and math functions.
Function Trait Required 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
Powabletrait also means that the type can be used with the power operator (**). -
For
ceildiv(), structs can conform to either theCeilDivabletrait orCeilDivableRaisingtrait. -
Due to ongoing refactoring, the traits
Ceilable,CeilDivable,Floorable, andTruncabledo not appear in the API reference. They should be imported from themathmodule, except forTruncablewhich 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)) -
-
-
Benchmarking:
- The
benchermodule as part of thebenchmarkpackage is now public and documented. This module provides types such asBencherwhich provides the ability to execute aBenchmarkand allows for benchmarking configuration via theBenchmarkConfigstruct.
- The
-
Stringand friends:-
Breaking. Implicit conversion to
Stringis now removed for builtin classes/types. Usestr()explicitly to convert toString. -
Added
String.isspace()method conformant with Python's universal separators. This replaces theisspace()free function from thestringmodule. (If you need the old function, it is temporarily available as_isspace(). It now takes aUInt8but 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()andrstrip()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) -
Stringnow has asplitlines()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) -
InlinedStringhas been renamed toInlineStringto be consistent with other types. -
StringRefnow implementsstrip(), which can be used to remove leading and trailing whitespace. (PR #2683) -
StringRefnow implementsstartswith()andendswith(). (PR #2710) -
Added a new
StringSlicetype, to replace uses of the unsafeStringReftype in standard library code.StringSliceis a non-owning reference to encoded string data. UnlikeStringRef, aStringSliceis safely tied to the lifetime of the data it points to.- Added new
as_string_slice()methods toStringandStringLiteral. - Added
StringSliceinitializer from anUnsafePointerand a length in bytes.
- Added new
-
Added a new
as_bytes_slice()method toStringandStringLiteral, which returns aSpanof the bytes owned by the string. -
Continued transition to
UnsafePointerand unsigned byte type for strings:- Renamed
String._as_ptr()toString.unsafe_ptr(), and changed return type toUnsafePointer(wasDTypePointer). - Renamed
StringLiteral.data()toStringLiteral.unsafe_ptr(), and changed return type toUnsafePointer(wasDTypePointer). InlineString.as_ptr()has been renamed tounsafe_ptr()and now returns anUnsafePointer[UInt8](wasDTypePointer[DType.int8]).StringRef.datais now anUnsafePointer(wasDTypePointer) andStringRef.unsafe_ptr()now returns anUnsafePointer[UInt8](wasDTypePointer[DType.int8]).
- Renamed
-
-
Other built-ins:
-
The
Slice.__len__()function has been removed andSliceno longer conforms to theSizedtrait. 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 theSlice.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 theComparableCollectionElementtrait.(PR #2609) -
int()can now take a string and a specified base to parse an integer from a string:int("ff", 16)returns255. 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 aStringto afloat64. (PR #2649) -
You can now use the built-in
any()andall()functions to check for truthy elements in a collection. BecauseSIMD.__bool__()is now constrained tosize=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 ofSIMDtoBool. (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") -
objectnow implements all the bitwise operators. (PR #2324) -
Tuplenow supports__contains__(). (PR #2709) For example:var x = Tuple(1, 2, True)
if 1 in x:
print("x contains 1") -
ListLiteralandTuplenow only require that element types beMovable. Consequently,ListLiteralandTupleare themselves no longerCopyable. -
Added new
ImmutableStaticLifetimeandMutableStaticLifetimehelpers.
-
-
UnsafePointerand others:-
Added new
memcpy()overload forUnsafePointer[Scalar[_]]pointers. -
Removed the
get_null()method fromUnsafePointerand 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 tounsafe_ptr(). The return type is still aDTypePointer[T].
-
-
Collections:
-
Listnow has anindex()method that allows you to find the (first) location of an element in aListofEqualityComparabletypes. For example:var my_list = List[Int](2, 3, 5, 7, 3)
print(my_list.index(3)) # prints 1 -
Listcan now be converted to aStringwith a simplified syntax:var my_list = List[Int](2, 3)
print(my_list.__str__()) # prints [2, 3]Note that
Listdoesn't conform to theStringabletrait yet so you cannot usestr(my_list)yet. (PR #2673) -
Listhas a simplified syntax to call thecount()method:my_list.count(x). (PR #2675) -
List()now supports__contains__(), so you can now use lists with theinoperator:if x in my_list:(PR #2667)
-
Listnow has anunsafe_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 toDictto return aDictwith the specified keys and values. (PR 2622) -
Dictnow supportsreversed()for itsitems()andvalues()iterators. (PR #2340) -
Dictnow has a simplified conversion toStringwithmy_dict.__str__(). Note thatDictdoes not conform to theStringabletrait sostr(my_dict)is not possible yet. (PR #2674) -
Dictnow implementsget(key)andget(key, default)functions. (PR #2519) -
Added a temporary
__get_ref(key)method toDict, allowing you to get aReferenceto a dictionary value. -
Added a new
InlineListtype, a stack-allocated list with a static maximum size. (PR 2587#) (PR #2703) -
Added a new
Spantype for taking slices of contiguous collections. (PR #2595)
-
-
osmodule:-
The
osmodule now provides functionality for adding and removing directories usingmkdir()andrmdir(). (PR #2430) -
Added the
os.path.getsize()function, which gives the size in bytes of the file identified by the path. (PR 2626) -
Added
os.path.join()function. (PR 2792) -
Added a new
tempfilemodule, withgettempdir()andmkdtemp()functions. (PR 2742)
-
-
SIMDtype:-
Added
SIMD.shuffle()withIndexListmask. (PR #2315) -
SIMD.__bool__()is constrained such that it only works whensizeis1. For SIMD vectors with more than one element, useany()orall(). (PR #2502) -
The
SIMD.reduce_or()andSIMD.reduce_and()methods are now bitwise operations, and support integer types. (PR #2671) -
Added
SIMD.__repr__()to get the verbose string representation ofSIMDtypes. (PR #2728)
-
-
mathpackage:-
The
math.bitmodule has been moved to a new top-levelbitmodule. The following functions in this module have been renamed:ctlz->countl_zerocttz->countr_zerobit_length->bit_widthctpop->pop_countbswap->byte_swapbitreverse->bit_reverse
-
The
math.rotate_bits_left()andmath.rotate_bits_right()functions have been moved to thebitmodule. -
The
is_power_of_2()function in themathmodule is now calledis_power_of_two()and located in thebitmodule. -
The
abs(),round(),min(),max(),pow(), anddivmod()functions have moved frommathtobuiltin, so you no longer need to import these functions. -
The
math.tgamma()function has been renamed tomath.gamma()to conform with Python's naming. -
The implementation of the following functions have been moved from the
mathmodule to the newutils.numericsmodule:isfinite(),isinf(),isnan(),nan(),nextafter(), andulp(). The functions continue to be exposed in themathmodule. -
math.gcd()now works on negative inputs, and like Python's implementation, accepts a variadic list of integers. New overloads for aListorSpanof integers are also added. (PR #2777)
-
-
Async and coroutines:
-
Coroutinenow 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
InlineArraytype that works on memory-only types. Compare with the existingStaticTupletype, which is conceptually an array type, but only works on__TypeOfAllTypes. (PR #2294) -
The
base64package now includes encoding and decoding support for both the Base64 and Base16 encoding schemes. (PR #2364) (PR #2584) -
The
take()function inVariantandOptionalhas been renamed tounsafe_take(). -
The
get()function inVarianthas been replaced by__getitem__(). That is,v.get[T]()should be replaced withv[T]. -
Various functions in the
algorithmmodule are now built-in functions. This includessort(),swap(), andpartition().swap()andpartition()will likely shuffle around as we're reworking our built-insort()function and optimizing it.
-
-
infinityandNaNare now correctly handled intesting.assert_almost_equal()and aninffunction has been added toutils/numerics.mojo. (PR #2375)
Tooling changes
-
Invoking
mojo package my-package -o my-diron the command line, wheremy-packageis a Mojo package source directory, andmy-diris an existing directory, now outputs a Mojo package tomy-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
mojosubcommands now support a--diagnostic-formatoption that changes the format with which errors, warnings, and other diagnostics are printed. By specifying--diagnostic-format jsonon 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-formatis as follows:mojo build,mojo doc,mojo run,mojo package, andmojo test. Further, themojo test --jsonoption has been subsumed into this new option; for the same behavior, runmojo 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-stringsoption has been added tomojoto emit errors on invalid doc strings instead of warnings. -
The
--warn-missing-doc-stringsflag formojohas 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 ofmojo 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
@unrolldecorator 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@unrolldecorator did not make the loop induction variables parameter values, limiting its usefulness. Please see@parameter forfor a replacement! -
The method
object.print()has been removed. Sinceobjectnow conforms to theStringabletrait, you can useprint(my_object)instead. -
The following functions have been removed from the math module:
clamp(); use the newSIMD.clamp()method instead.round_half_down()andround_half_up(); these can be trivially implemented using theceil()andfloor()functions.add(),sub(),mul(),div(),mod(),greater(),greater_equal(),less(),less_equal(),equal(),not_equal(),logical_and(),logical_xor(), andlogical_not(); Instead, users should rely directly on the corresponding operators (+,-,*,/,%,>,>=,<,<=,==,!=,&,^, and~).identity()andreciprocal(); users can implement these trivially.select(); removed in favor of usingSIMD.select()directly.is_even()andis_odd(); these can be trivially implemented using bitwise&with1.roundeven(); the newSIMD.roundeven()method now provides the identical functionality.div_ceil(); use the newceildiv()function.rotate_left()androtate_right(); the same functionality is available in the builtinSIMD.rotate_{left,right}()methods forSIMDtypes, and thebit.rotate_bits_{left,right})()methods forInt.- An overload of
math.pow()taking an integer parameter exponent. align_down_residual(); it can be trivially implemented usingalign_down().all_true(),any_true(), andnone_true(); useSIMD.reduce_and()andSIMD.reduce_or()directly.reduce_bit_count(); use the newSIMD.reduce_bit_count()directly.rint()andnearbyint(); useround()orSIMD.roundeven()as appropriate.
-
The
EvaluationMethodhas been removed frommath.polynomialand 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 ofmath.polynomial.polynomial_evaluate()is unchanged (Horner's method). -
The
math.bit.select()andmath.bit.bit_and()functions have been removed. The same functionality is available in the builtinSIMD.selectandSIMD.__and__()methods, respectively. -
The
math.limitmodule has been removed. The same functionality is available as follows:math.limit.inf(): useutils.numerics.max_or_inf()math.limit.neginf(): useutils.numerics.min_or_neg_inf()math.limit.max_finite(): useutils.numerics.max_finite()math.limit.min_finite(): useutils.numerics.min_finite()
-
The
tensor.randommodule has been removed. The same functionality is now accessible via theTensor.rand()andTensor.randn()static methods. -
The builtin
SIMDstruct no longer conforms toIndexer; users must explicitly castScalarvalues usingint.
🛠️ Fixed
- #1837 Fix self-referential variant crashing the compiler.
- #2363 Fix LSP crashing on simple trait definitions.
- #1787 Fix error when using
//onFloatLiteralin 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_raisesto 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