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
traitkeyword. 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
Destructabletrait 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 toAnyTypeso 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(), andint()functions, which work with types that implement theSized,Stringable, andIntabletraits, respectively. -
DynamicVectoris now a proper generic collection that can use any type that implements theMovableandCopyabletraits. This means you can now write, for example,DynamicVector[String]. Also,DynamicVectornow invokes its element destructors upon destruction, so_del_oldhas been deleted. -
printnow works on any types that implementStringableby 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 parameterizedIn the first signature for
eat(), thebparameter 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 beb).Both functions can be called like this:
eat(Fudge[5, 8]())Mojo infers the value of the
bparameter from the argument (in this case, 8).With the second signature, you can also pass the
_bparameter 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 parameterizedThe first two signatures explicitly unbind the
bandcparameters.In the last signature, the
_band_cparameters are explicitly declared by the author, and bound to thebandcparameters 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
Scalartype alias has been added defined as:alias Scalar = SIMD[size=1]Which creates a parametric type alias
Scalarwith a single parameter of typeDType. Types can also be partially or fully bound in other contexts. For instance,aliasdeclarations 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_opfeature now supports operations that return multiple results. To use them, you write the_typefield as aTupleof 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()andread_bytes()methods on the builtinfile.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) -
Pathnow hasread_bytes()andread_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() -
Tensorhas newsave()andload()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
DTypePointerandPointer:let p = DTypePointer[DType.float16].alloc(4)
for i in range(4):
p[i] = i
print(p[i]) -
file.FileHandlenow has aseek()method. -
Stringnow has anrfind()method analogous to Python'sstr.rfind(). -
Stringnow has ansplit()method analogous to Python'sstr.split(). -
Pathnow has asuffix()method analogous to Python'spathlib.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
importstatements. -
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
mojodriver that take arguments can now be written in either of two ways: both--foo FOOand--foo=FOO. Previously, only the former was valid.
🦋 Changed
-
Variadic list types
VariadicListandVariadicListMemare 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
testingpackage now raise anErrorwhen the assertion fails instead of returning aBoolfor whether the assertion succeeded or not. -
Parameters of
AnyTypetype are no longer (implicitly) assumed to be register-passable. A newAnyRegTypetype is used to represent generic types that are register passable. -
Changing the units in a
benchmarkreport is now an argument instead of a parameter:let report = benchmark.run[timer]()
report.print(Unit.ms) -
Default values on
inoutarguments 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 fromPythonObjectin favor of the new__str__()function. This composes better with traits so it can be used with the genericstr()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.