#set document(title: "2.11 Modules and packages", 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.11#h(0.6em)Modules and packages This page describes how to organize your project into modules (files) and packages (directories) that you can import into other Mojo code (and into Python code). If you want to package your project for distribution, instead see the #link("https://mojolang.org/docs/tools/packaging/")[Packaging guide]. === Mojo modules To understand Mojo packages, you first need to understand Mojo modules. A Mojo module is a single Mojo source file that includes code suitable for use by other files that import it. For example, you can create a module to define a struct such as this one: struct MyPair: var first: Int var second: Int def \_\_init\_\_(out self, first: Int, second: Int): self.first = first self.second = second def dump(self): print(self.first, self.second) Notice that this code has no main() function, so you can't execute mymodule.mojo. However, you can import this into another file with a main() function and use it there. For example, here's how you can import MyPair into a file named main.mojo that's in the same directory as mymodule.mojo: from mymodule import MyPair def main(): var mine = MyPair(2, 4) mine.dump() Alternatively, you can import the whole module and then access its members through the module name. For example: import mymodule def main(): var mine = mymodule.MyPair(2, 4) mine.dump() You can also create an alias for an imported member with as, like this: import mymodule as my def main(): var mine = my.MyPair(2, 4) mine.dump() In this example, it only works when mymodule.mojo is in the same directory as main.mojo. Currently, you can't import .mojo files as modules if they reside in other directories. That is, unless you treat the directory as a Mojo package, as described in the next section. #notebox("Note", rgb("#8a94a6"), rgb("#556666"), rgb("#f7f8fa"))[ A Mojo module may include a main() function and may also be executable, but that's generally not the practice and modules typically include APIs to be imported and used in other Mojo programs. ] === Mojo packages A Mojo package is just a collection of Mojo modules in a directory that includes an \_\_init\_\_.mojo file. By organizing modules together in a directory, you can then import all the modules together or individually. Optionally, you can also compile the package into a precompiled .mojoc file that's quicker to load when used as a dependency to another Mojo compile. You can import a package and its modules either directly from source files or from a compiled .mojoc file. It makes no real difference to Mojo which way you import a package. When importing from source files, the directory name works as the package name, whereas when importing from a compiled package, the filename is the package name (which you specify with the #link("https://mojolang.org/docs/cli/precompile")[mojo precompile] command—it can differ from the directory name). For examples, see the section below about naming and identifiers. For example, consider a project with these files: main.mojo mypackage/ \_\_init\_\_.mojo mymodule.mojo mymodule.mojo is the same code from examples above (with the MyPair struct) and \_\_init\_\_.mojo is empty. #notebox("Note", rgb("#8a94a6"), rgb("#556666"), rgb("#f7f8fa"))[ The \_\_init\_\_.mojo file is essential. If you don't have it, Mojo won't recognize the directory as a package and you can't import mymodule. ] In this case, the main.mojo file can now import MyPair through the package name like this: from mypackage.mymodule import MyPair def main(): var mine = MyPair(2, 4) mine.dump() This immediately works: mojo main.mojo 2 4 However, if you don't want the mypackage source code in the same location as main.mojo, you can compile it into a precompiled file like this: mojo precompile mypackage -o mypack.mojoc #notebox("Note", rgb("#8a94a6"), rgb("#556666"), rgb("#f7f8fa"))[ A .mojoc file contains non-elaborated code, so you #emph[can] share it across systems. The code becomes an architecture-specific executable only after it's imported into a Mojo program that's then compiled with mojo build. The .mojoc format is not intended as a generic distributable format, however, as it is tied to the exact version of the compiler that produced it. Loading a .mojoc file produced by one version of the compiler into another version of the compiler will result in a compiler error. ] Now, you can move the mypackage source somewhere else, and the project files now look like this: main.mojo mypack.mojoc Because we named the package mypack, we need to fix the import statement: from mypack.mymodule import MyPair And the code works the same: mojo main.mojo 2 4 #notebox("Note", rgb("#8a94a6"), rgb("#556666"), rgb("#f7f8fa"))[ If you want to rename your package, you cannot simply edit the .mojoc filename, because the package name is encoded in the file. You must instead run mojo precompile again to specify a new name. ] ==== The \_\_init\_\_ file As mentioned above, the \_\_init\_\_.mojo file is required to indicate that a directory should be treated as a Mojo package, and it can be empty. Currently, top-level code is not supported in .mojo files, so unlike Python, you can't write code in \_\_init\_\_.mojo that executes upon import. You can, however, add structs and functions, which you can then import from the package name. However, instead of adding APIs in the \_\_init\_\_.mojo file, you can import module members, which has the same effect by making your APIs accessible from the package name, instead of requiring the \.\ notation. For example, again let's say you have these files: main.mojo mypackage/ \_\_init\_\_.mojo mymodule.mojo Let's now add the following line in \_\_init\_\_.mojo: from .mymodule import MyPair That's all that's in there. Now, we can simplify the import statement in main.mojo like this: from mypackage import MyPair This feature explains why some members in the Mojo standard library can be imported from their package name, while others required the \.\ notation. For example, the #link("https://mojolang.org/docs/std/algorithm/functional/")[functional] module resides in the std.algorithm package, so you can import members of that module (such as the map() function) like this: from std.algorithm.functional import map However, the algorithm/\_\_init\_\_.mojo file also includes these lines: from .functional import \* from .reduction import \* So you can actually import anything from functional or reduction simply by naming the package. That is, you can drop the functional name from the import statement, and it also works: from std.algorithm import map #notebox("Note", rgb("#8a94a6"), rgb("#556666"), rgb("#f7f8fa"))[ Which modules in the standard library are imported to the package scope varies, and is subject to change. Refer to the #link("https://mojolang.org/docs/std/")[documentation for each module] to see how you can import its members. ] ==== Package naming and identifiers Package names are taken from directory names in the case of source packages, or the precompiled (.mojoc) filename for binary ones. Note that if the package name is not a valid identifier, an escaped identifier may be used instead: import \`модул\` import \`package-with-hyphens and a space!\` as package\_without\_hyphens\_or\_a\_space def main(): \`модул\`.\`здрасти\` package\_without\_hyphens\_or\_a\_space.hello()