#set document(title: "2.1 Mojo language basics", author: "Modular Inc. / XYZ Homework") #set page(width: 8.5in, height: auto, margin: 1in) #import "@preview/cetz:0.5.2" #set text(font: ("STIX Two Text", "Libertinus Serif", "New Computer Modern"), size: 10.5pt, lang: "en") #show math.equation: set text(font: ("STIX Two Math", "New Computer Modern Math")) #set par(justify: true, leading: 0.62em, spacing: 0.9em) #set enum(spacing: 1.1em) // room between list items so tall inline fractions don't collide #set list(spacing: 1.1em) #set table(stroke: 0.5pt + rgb("#c7ccd3")) #let BLUE = rgb("#183B6F") // brand navy — section bars + example/solution labels (white on navy 11.09:1) #let ORANGE = rgb("#A94509") // brand primary-700 — AA-safe deep orange for TEXT (5.93:1 on white; raw brand #F37021 is 2.94:1 and must never carry text) #let RED = rgb("#DC2626") // brand error-600 #let GREEN = rgb("#059669") // brand success-600 (decoration only; small green text uses green-text #007942) #show heading.where(level: 1): it => block(width: 100%, above: 0pt, below: 16pt, fill: gradient.linear(BLUE, rgb("#2C5AA0")), inset: (x: 14pt, y: 12pt), radius: 3pt, text(fill: white, weight: "bold", size: 19pt, it.body)) #show heading.where(level: 2): it => block(width: 100%, above: 18pt, below: 10pt, fill: BLUE, inset: (x: 10pt, y: 6pt), radius: 2pt, text(fill: white, weight: "bold", size: 12pt, it.body)) #show heading.where(level: 3): it => text(fill: ORANGE, weight: "bold", size: 12.5pt, it.body) #show heading.where(level: 4): it => text(fill: BLUE, weight: "bold", size: 10.5pt, it.body) #let examplebox(label, title, body) = block(width: 100%, breakable: true, fill: rgb("#EFF1F5"), stroke: 0.5pt + rgb("#CFDDF0"), radius: 4pt, inset: 10pt, above: 12pt, below: 12pt)[ #block(below: 6pt)[#box(fill: BLUE, inset: (x: 6pt, y: 2pt), radius: 2pt, text(fill: white, weight: "bold", size: 8.5pt, label)) #h(0.4em) #strong[#title]] #body] // rail = decorative left rule (raw brand token); labelcolor = AA-safe label text shade #let notebox(label, rail, labelcolor, tint, body) = block(width: 100%, breakable: true, fill: tint, stroke: (left: 3pt + rail), inset: (left: 10pt, rest: 8pt), radius: (right: 4pt), above: 11pt, below: 11pt)[ #text(fill: labelcolor, weight: "bold", size: 7.5pt, tracking: 0.5pt)[#upper(label)] #linebreak() #body] #let solutionbox(body) = block(above: 4pt, below: 8pt)[ #text(fill: BLUE, weight: "bold", size: 8.5pt)[Solution] #linebreak() #body] #let figph(msg) = block(width: 100%, height: 60pt, fill: rgb("#f6f7f9"), stroke: (paint: rgb("#c7ccd3"), dash: "dashed"), radius: 4pt, inset: 10pt)[ #align(center + horizon, text(fill: rgb("#889"), style: "italic", size: 9pt, msg))] // Standardize inlined figure sizes: measure the natural CeTZ canvas, then scale to a // consistent envelope (aspect-aware; see build_typst.py FIG_* constants). Unlike the // print preamble, dimensions are FLOORED: in an editor a user can trim a figure to a // degenerate 1-D shape (a bare line), and w/h or tw/w would then divide by zero. #let _STD_W = 3.5 #let _WIDE_W = 5.6 #let _MAX_H = 3.4 #let _ASPECT_WIDE = 2.2 #let _UPSCALE_MAX = 1.15 #let stdfig(body) = context { let m = measure(body) let w = calc.max(m.width / 1in, 0.01) let h = calc.max(m.height / 1in, 0.01) let tw = if w / h > _ASPECT_WIDE { _WIDE_W } else { _STD_W } let s = calc.min(tw / w, _MAX_H / h, _UPSCALE_MAX) align(center, box(scale(x: s * 100%, y: s * 100%, reflow: true, body))) } #show figure: set block(breakable: false) #set figure(gap: 8pt) #show figure.caption: set text(size: 8.5pt, fill: rgb("#555")) == 2.1#h(0.6em)Mojo language basics This page provides an overview of the Mojo language. If you know Python, then a lot of Mojo code will look familiar. However, Mojo incorporates features like static type checking, memory safety, next-generation compiler technologies, and more. As such, Mojo also has a lot in common with languages like C++ and Rust. If you prefer to learn by doing, follow the Get started with Mojo tutorial. On this page, we'll introduce the essential Mojo syntax, so you can start coding quickly and understand other Mojo code you encounter. Subsequent sections in the Mojo Manual dive deeper into these topics, and links are provided below as appropriate. Let's get started! 🔥 #notebox("Note", rgb("#8a94a6"), rgb("#556666"), rgb("#f7f8fa"))[ Mojo is a young language and there are many #link("https://mojolang.org/docs/roadmap")[features still missing]. As such, Mojo is currently #strong[not] meant for beginners. Even this basics section assumes some programming experience. However, throughout the Mojo Manual, we try not to assume experience with any particular language. ] === Hello world Here's the traditional "Hello world" program in Mojo: def main(): print("Hello, world!") Hello, world! Every Mojo program must include a function named main() as the entry point. We'll talk more about functions soon, but for now it's enough to know that you can write def main(): followed by an indented function body. The print() statement does what you'd expect, printing its arguments to the standard output. === Variables In Mojo, you can declare a variable by simply assigning a value to a new named variable: def main(): x = 10 y = x \* x print(y) 100 You can also #emph[explicitly] declare variables with the var keyword: var x = 10 When declaring a variable with var, you can also declare a variable type, with or without an assignment: def main(): var x: Int = 10 var sum: Int sum = x + x Both implicitly declared and explicitly declared variables are statically typed: that is, the type is set at compile time, and doesn't change at runtime. If you don't specify a type, Mojo uses the type of the first value assigned to the variable. x = 10 x = "Foo" \# Error: Cannot convert "StringLiteral" value to "Int" For more details, see the page about variables. === Blocks and statements Code blocks such as functions, conditions, and loops are defined with a colon followed by indented lines. For example: def loop(): for x in range(5): if x % 2 == 0: print(x) You can use any number of spaces or tabs for your indentation (we prefer 4 spaces). All code statements in Mojo end with a newline. However, statements can span multiple lines if you indent the following lines. For example, this long string spans two lines: def print\_line(): long\_text = "This is a long line of text that is a lot easier to read if" " it is broken up across two lines instead of one long line." print(long\_text) And you can chain function calls across lines: def print\_hello(): text = ",".join("Hello", " world!") print(text) For more information on loops and conditional statements, see Control flow. === Functions You define Mojo functions with the def keyword. For example, the following uses the def keyword to define a function named greet that requires a single String argument and returns a String: def greet(name: String) -\> String: return "Hello, " + name + "!" === Code comments You can create a one-line comment using the hash \# symbol: \# This is a comment. The Mojo compiler ignores this line. Comments may also follow some code: var message = "Hello, World!" \# This is also a valid comment API documentation comments are enclosed in triple quotes. For example: def print(x: String): """Prints a string. Args: x: The string to print. """ ... Documenting your code with these kinds of comments (known as "docstrings") is a topic we've yet to fully specify, but you can generate an API reference from docstrings using the #link("https://mojolang.org/docs/cli/doc/")[mojo doc command]. #notebox("Note", rgb("#8a94a6"), rgb("#556666"), rgb("#f7f8fa"))[ Technically, docstrings aren't #emph[comments], they're a special use of Mojo's syntax for multi-line string literals. For details, see String literals in the page on Types. ] === Structs You can build high-level abstractions for types (or "objects") as a struct. A struct in Mojo is similar to a class in Python: they both support methods, fields, operator overloading, decorators for metaprogramming, and so on. However, Mojo structs are completely static—they are bound at compile-time, so they do not allow dynamic dispatch or any runtime changes to the structure. (Mojo will also support Python-style classes in the future.) For example, here's a basic struct: struct MyPair(Copyable): var first: Int var second: Int def \_\_init\_\_(out self, first: Int, second: Int): self.first = first self.second = second def \_\_init\_\_(out self, \*, copy: Self): self.first = copy.first self.second = copy.second def dump(self): print(self.first, self.second) And here's how you can use it: def use\_mypair(): var mine = MyPair(2, 4) mine.dump() The MyPair struct contains two special methods, \_\_init\_\_(), the constructor, and \_\_init\_\_(out self, \*, copy: Self), the copy constructor. #emph[Lifecycle methods] like this control how a struct is created, copied, moved, and destroyed. For most simple types, you don't need to write the lifecycle methods. You can use the \@fieldwise\_init decorator to generate the boilerplate field-wise initializer for you, and Mojo will synthesize copy and move constructors if you ask for them with trait conformance. So the MyPair struct can be simplified to this: \@fieldwise\_init struct MyPair(Copyable): var first: Int var second: Int def dump(self): print(self.first, self.second) For more details, see the page about structs. ==== Traits A trait is like a template of characteristics for a struct. If you want to create a struct with the characteristics defined in a trait, you must implement each characteristic (such as each method). Each characteristic in a trait is a "requirement" for the struct, and when your struct implements all of the requirements, it's said to "conform" to the trait. Using traits allows you to write generic functions that can accept any type that conforms to a trait, rather than accept only specific types. For example, here's how you can create a trait: trait SomeTrait: def required\_method(self, x: Int): ... The three dots following the method signature are Mojo syntax indicating that the method is not implemented. Here's a struct that conforms to SomeTrait: \@fieldwise\_init struct SomeStruct(SomeTrait): def required\_method(self, x: Int): print("hello traits", x) Then, here's a function that uses the trait as an argument type (instead of the struct type): def fun\_with\_traits\[T: SomeTrait\](x: T): x.required\_method(42) def use\_trait\_function(): var thing = SomeStruct() fun\_with\_traits(thing) You'll see traits used in a lot of APIs provided by Mojo's standard library. For example, Mojo's collection types like List and Dict can store any type that conforms to the Copyable trait. You can specify the type when you create a collection: my\_list = List\[Float64\]() #notebox("Note", rgb("#8a94a6"), rgb("#556666"), rgb("#f7f8fa"))[ You're probably wondering about the square brackets on fun\_with\_traits(). These aren't function #emph[arguments] (which go in parentheses); these are function #emph[parameters], which we'll explain next. ] Without traits, the x argument in fun\_with\_traits() would have to declare a specific type that implements required\_method(), such as SomeStruct (but then the function would accept only that type). With traits, the function can accept any type for x as long as it conforms to (it "implements") SomeTrait. Thus, fun\_with\_traits() is known as a "generic function" because it accepts a #emph[generalized] type instead of a specific type. For more details, see the page about traits. === Parameterization In Mojo, a parameter is a compile-time variable that becomes a runtime constant, and it's declared in square brackets on a function or struct. Parameters allow for compile-time metaprogramming, which means you can generate or modify code at compile time. Many other languages use "parameter" and "argument" interchangeably, so be aware that when we say things like "parameter" and "parametric function," we're talking about these compile-time parameters. Whereas, a function "argument" is a runtime value that's declared in parentheses. Parameterization is a complex topic that's covered in much more detail in the Metaprogramming section, but we want to break the ice just a little bit here. To get you started, let's look at a parametric function: def repeat\[count: Int\](msg: String): \# evaluate the following for loop at compile time comptime for i in range(count): print(msg) This function has one parameter of type Int and one argument of type String. To call the function, you need to specify both the parameter and the argument: def call\_repeat(): repeat\[3\]("Hello") \# Prints "Hello" 3 times By specifying count as a parameter, the Mojo compiler is able to optimize the function because this value is guaranteed to not change at runtime. And the comptime keyword in the code tells the compiler to evaluate the for loop at compile time, not runtime. The compiler effectively generates a unique version of the repeat() function that repeats the message only 3 times. This makes the code more performant because there's less to compute at runtime. Similarly, you can define a struct with parameters, which effectively allows you to define variants of that type at compile-time, depending on the parameter values. For more detail on parameters, see the section on Metaprogramming. === Python integration Mojo supports the ability to import Python modules as-is, so you can leverage existing Python code right away. For example, here's how you can import and use NumPy: from std.python import Python def main() raises: var np = Python.import\_module("numpy") var ar = np.arange(15).reshape(3, 5) print(ar) print(ar.shape) You must have the Python module (such as numpy) installed in the environment where you're using Mojo. For more details, see the page on Python integration. === Next steps Hopefully this page has given you enough information to start experimenting with Mojo, but this is only touching the surface of what's available in Mojo. If you're in the mood to read more, continue through each page of this Mojo Manual—the next page from here is Functions. Otherwise, here are some other resources to check out: - See Get started with Mojo for a hands-on tutorial that will get you up and running with Mojo. - If you want to experiment with some code, clone #link("https://github.com/modular/modular/")[our GitHub repo] to try our code examples: git clone https://github.com/modular/modular.git cd mojo/examples - To see all the available Mojo APIs, check out the #link("https://mojolang.org/docs/std")[Mojo standard library reference].