# Mojo programming language documentation > Official documentation for the Mojo programming language, including the manual, language reference, standard library API docs, and more. Version: 1.1.0 ## Install Mojo Mojo installs exactly like a Python or Conda package on macOS and Linux (see the [system requirements](/docs/requirements/)). 1. If needed, install `uv`: ```sh curl -LsSf https://astral.sh/uv/install.sh | sh ``` 2. Install Mojo: ```sh uv pip install mojo \ --index https://whl.modular.com/nightly/simple/ \ --prerelease allow ``` Or create a project and install Mojo: ```sh uv init hello-world cd hello-world uv add mojo \ --index https://whl.modular.com/nightly/simple/ \ --prerelease allow ``` 1. If needed, install `pixi`: ```sh curl -fsSL https://pixi.sh/install.sh | sh ``` 2. Create a project and install Mojo: ```sh pixi init hello-world \ -c https://conda.modular.com/max-nightly/ -c conda-forge cd hello-world pixi add mojo ``` These commands install the nightly build, which isn't complete and might have new bugs. If you instead want the stable build, switch to the stable docs. 1. If needed, install `uv`: ```sh curl -LsSf https://astral.sh/uv/install.sh | sh ``` 2. Install Mojo: ```sh uv pip install mojo ``` Or create a project and install Mojo: ```sh uv init hello-world cd hello-world uv add mojo ``` 1. If needed, install `pixi`: ```sh curl -fsSL https://pixi.sh/install.sh | sh ``` 2. Create a project and install Mojo: ```sh pixi init hello-world \ -c https://conda.modular.com/max/ -c conda-forge cd hello-world pixi add mojo ``` If you instead want the latest nightly build, switch to the nightly docs. ## Get the VS Code extension For syntax highlighting, code completion, and debugging support, install the Mojo extension from: - [VS Code Marketplace](https://marketplace.visualstudio.com/items?itemName=modular-mojotools.vscode-mojo) - [Open VSX Registry](https://open-vsx.org/extension/modular-mojotools/vscode-mojo) ## Get Mojo skills Mojo agent skills keep your AI coding assistants aligned with the latest language updates and best practices. Install with: ```sh npx skills add modular/skills ``` ## Get started --- ## Mojo FAQ We tried to anticipate your questions about Mojo on this page. If this page doesn't answer all your questions, see [Mojo vision](/docs/vision/) for the history and motivation behind the Mojo language, and the [roadmap](/docs/roadmap) for a high-level view of what's next for Mojo. ## Motivation ### Why did you build Mojo? We built Mojo to solve an internal challenge when building the [Modular Platform](https://www.modular.com)—programming across the entire stack was too complicated. We wanted a flexible and scalable programming model that could target CPUs, GPUs, AI accelerators, and other heterogeneous systems that are pervasive in the AI field. This meant a programming language with powerful compile-time metaprogramming, integration of adaptive compilation techniques, caching throughout the compilation flow, and other features that existing languages don't support. As a result, we're extremely committed to Mojo's long-term success and are investing heavily in it. Our overall mission is to unify AI software and we can't do that without a unified language that can scale across the whole AI infrastructure stack. Our current focus is to unify CPU and GPU programming with blazing-fast execution for the Modular Platform. That said, the north star is for Mojo to support the whole gamut of general-purpose programming over time. For more detail, and insight into why we built Mojo the way we did, see the [Mojo vision](/docs/vision/). ### Why is it called Mojo? Mojo means "a magical charm" or "magical powers." We thought this was a fitting name for a language that brings magical powers to programmers, including unlocking an innovative programming model for accelerators and other heterogeneous systems pervasive in AI today. ## Functionality ### Where can I learn more about Mojo's features? The best place to start is the [Mojo Manual](/docs/manual/). And if you want to see what features are coming in the future, take a look at [the roadmap](/docs/roadmap). ### Is Mojo only for AI, or can I use it for other things? Mojo's initial focus was to solve AI programmability challenges. However, our goal is to grow Mojo into a general-purpose programming language. We use Mojo at Modular to develop AI algorithms and GPU kernels, but you can use it for other things like HPC, data transformations, writing pre/post processing operations, libraries, and much more. See the community page to get inspired by the projects others are writing in Mojo! ### Is Mojo interpreted or compiled? Mojo is a compiled language. [`mojo build`](/docs/cli/build/) and [`mojo run`](/docs/cli/run/) both perform ahead-of-time (AOT) compilation. ### Does Mojo support distributed execution? Not alone. Mojo is one component of the Modular Platform, which makes it easier for you to author highly performant, portable CPU and GPU graph operations, but you'll also need a runtime (or "OS") that supports graph-level transformations and heterogeneous compute, which the [MAX framework](https://max.modular.com) provides. ### How do I convert Python programs or libraries to Mojo? See [Tips for Python devs](/docs/manual/python-to-mojo/) for a quick primer on important differences between Python and Mojo. The [Mojo AI skills](/docs/tools/skills/) can help your AI coding assistant translate Python code into working Mojo code. You can also migrate parts of a Python project to Mojo by building Mojo bindings for Python. See the documentation about how to [call Mojo from Python](/docs/manual/python/mojo-from-python). ### What about interoperability with other languages like C/C++? Mojo code is interoperable with C code. For information, see the docs for the [`ffi`](/docs/std/ffi/) module, the [`@export`](/docs/reference/decorators/export/) decorator, and the [`abi("C")` function effect](/docs/reference/function-declarations/#abi-c). Mojo code is also interoperable with C++ code that uses `extern "C"`. We believe we can deliver better C++ interoperability in the future. ### How does Mojo support hardware lowering? Mojo leverages LLVM-level dialects for the hardware targets it supports, and it uses other MLIR-based code-generation backends where applicable. This also means that Mojo is easily extensible to any hardware backend. ### Who writes the software to add more hardware support for Mojo? Mojo provides all the language functionality necessary for anyone to extend hardware support. As such, we expect hardware vendors and community members to contribute additional hardware support in the future. ## Performance ### Are there any AI-related performance benchmarks for Mojo? Remember that we designed Mojo as a general-purpose programming language, and any AI-related benchmarks rely heavily upon other framework components. For example, we write all of the in-house CPU and GPU graph operations that power the Modular Platform in Mojo. You can learn more about performance in our blog posts on [bringing the Modular Platform up on AMD MI355](https://www.modular.com/blog/achieving-state-of-the-art-performance-on-amd-mi355----in-just-14-days) and [optimizing matmul performance on the NVIDIA Blackwell GPU](https://www.modular.com/blog/matrix-multiplication-on-blackwell-part-4---breaking-sota). ## Mojo SDK ### How can I get the Mojo SDK? You can get Mojo and all the developer tools by installing `mojo` with any Python or Conda package manager. For details, see the [Mojo installation guide](/install/). ### What's included in the Mojo SDK? We actually offer two Mojo packages: `mojo` and `mojo-compiler`. The `mojo` package gives you everything you need for Mojo development. It includes: - [`mojo` CLI](/docs/cli/) (includes the Mojo compiler) - [Mojo standard library](/docs/std/) - [`mojo` Python package](https://github.com/modular/modular/tree/mojo/v1.1.0/Mojo/python/mojo) - Mojo language server (LSP) for IDE/editor integration - [Mojo debugger](/docs/tools/debugging/) (includes LLDB) - [Mojo code formatter](/docs/cli/format/) - [Mojo REPL](/docs/cli/repl/) The `mojo-compiler` package is smaller and is useful for environments where you only need to call or build existing Mojo code. For example, this is good if you're running Mojo in a production environment or if you're programming in Python and [calling a Mojo package](/docs/manual/python/mojo-from-python)—situations where you don't need the LSP and debugger tools. It includes: - [`mojo` CLI](/docs/cli/) (includes the Mojo compiler) - [Mojo standard library](/docs/std/) - [`mojo` Python package](https://github.com/modular/modular/tree/mojo/v1.1.0/Mojo/python/mojo) If you're interested in GPU programming, install the `max` package, which includes the MAX framework and Mojo. For details, see [Get started with GPU programming](https://max.modular.com/gpu/intro-tutorial/) in the MAX documentation. ### What are the license terms for the SDK? The Mojo SDK is licensed under the Apache License v2.0 with LLVM Exceptions. For details, see the [LICENSE](https://github.com/modular/modular/blob/mojo/v1.1.0/LICENSE). ### What operating systems does Mojo support? Mojo supports Mac and Linux natively and supports Windows via WSL. For details, see the [Mojo system requirements](/docs/requirements/). ### Is there IDE integration? Yes, we've published an official Mojo language extension for [Visual Studio Code](https://code.visualstudio.com/) and other editors that support VS Code extensions (such as [Cursor](https://cursor.com/home)). The extension supports various features including syntax highlighting, code completion, formatting, hover, etc. It works seamlessly with remote-ssh and dev containers to enable remote development in Mojo. You can obtain the extension from either the [Visual Studio Code Marketplace](https://marketplace.visualstudio.com/items?itemName=modular-mojotools.vscode-mojo) or the [Open VSX Registry](https://open-vsx.org/extension/modular-mojotools/vscode-mojo). ### Does the Mojo SDK collect telemetry? Yes, the Mojo SDK collects some basic system information, tool invocation events, crash reports, and some LSP performance events that enable us to identify, analyze, and prioritize Mojo issues. Specifically, we collect: - **Invocation events**: Each Mojo tool (the `mojo` CLI, the Mojo language server, and the Mojo debugger) reports a single event when it starts. The event includes the tool name and subcommand (such as `build` or `run`), and whether crash reporting is enabled. It does not include your command-line arguments, file names, or source code. - **Crash reports**: When a Mojo tool crashes, it uploads a crash report containing the stack trace of the crashed process, along with the tool name and the Mojo version, so we can attribute the crash to a specific release and fix it. - **LSP performance metrics**: The Mojo LSP reports aggregate data on how long it takes to respond to user input (parsing latency). The report includes only the milliseconds between user keystrokes and when the Mojo LSP is able to show appropriate error or warning messages. Every event also includes the Mojo/MAX version, basic system information (OS type and version; CPU architecture, model name, core count, and supported CPU features), and two anonymous identifiers: a machine identifier (a one-way hash, which cannot be reversed to identify you or your hardware) and a randomly generated per-session identifier. These identifiers let us count active installations and connect a crash report to its invocation event - for example, to compute a crash rate per release. We never collect or transmit any user information, such as source code, keystrokes, or any other user data. This telemetry is crucial to help us quickly identify problems and improve our products. Without this telemetry, we would have to rely on user-submitted bug reports, and in our decades of experience building developer products, we know that most people don't do that. The telemetry provides us the insights we need to build better products for you. Telemetry can be disabled by setting the environment variable `MODULAR_TELEMETRY_ENABLED=false`. ## Versioning & compatibility ### What's the Mojo versioning strategy? Starting with Mojo 1.0, Mojo follows semantic versioning for the core language and stable portions of the standard library. We consider language features stable unless we explicitly identify them as experimental or unstable. We consider standard library APIs **unstable** unless we explicitly identify them as stable. The API documentation identifies the stable APIs. For more information, see [Mojo stability guarantees](/docs/api-docs/stability/). See our [roadmap](/docs/roadmap/) to understand where things are headed. ### How often do you release new versions of Mojo? Mojo development is moving fast and we are regularly releasing updates. We aim to produce stable releases every six weeks, and nightly builds almost every night. Join the [Mojo Discord channel](http://discord.gg/modular) for notifications and [sign up for our newsletter](https://www.modular.com/blog#sign-up-for-our-newsletter) (on the bottom of the Mojo blog page) for more coarse-grained updates. ## Open source ### Is Mojo open source? Mojo is open source under the Apache License v2.0 with LLVM Exceptions. For details, see the [LICENSE](https://github.com/modular/modular/blob/mojo/v1.1.0/LICENSE). ### Why didn't you develop Mojo in the open from the beginning? Though we always intended to open source Mojo eventually, we started developing it in private and took a gradual approach to open sourcing the entire language. Mojo is a big project and has several architectural differences from previous languages. We believe a tight-knit group of engineers with a common vision can move faster than a community effort. Other projects that are now open source (such as LLVM, Clang, Swift, MLIR, etc.) also followed this well-established development approach. ## Community ### Where can I ask more questions or share feedback? If you have questions about upcoming features or have suggestions for the language, be sure you first read the [Mojo roadmap](/docs/roadmap/), which provides important information about our current priorities. To get in touch with the Mojo team and developer community, use the resources on our community page. --- ## Documentation export const getStartedCards = [ { icon: , title: 'Quickstart', url: '/docs/manual/quickstart/', description: 'Install and tour Mojo syntax basics in 20 minutes', }, { icon: , title: '\'Get Started\' Tutorial', url: '/docs/manual/get-started/', description: 'Build Conway\'s Game of Life from scratch', }, { icon: , title: 'Cheat Sheets', url: '/docs/reference/cheat-sheets/', description: 'Quick syntax and feature reference guides', }, ]; export const guidesReferencesCards = [ { title: 'Mojo Manual', url: '/docs/manual/', description: 'Syntax, concepts, programming patterns, and more', }, { title: 'Standard Library APIs', url: '/docs/std/', description: 'Data types, structs, traits, functions, and packages', }, { title: 'Mojo Language Reference', url: '/docs/reference/', description: 'Detailed guides and deep dives into Mojo syntax', }, { title: 'Agent Skills', url: '/docs/tools/skills/', description: 'Keep your AI coding assistant current with the latest Mojo', }, { title: 'Mojo Quest', url: 'https://quest.mojolang.org/', description: 'Fix tickets, learn Mojo: an interactive coding game', }, { title: 'Compiler Reference', url: '/docs/cli/', description: 'Commands and options for the `mojo` CLI compiler', }, ]; export const shortcutColumns = [ { icon: , title: 'Popular topics', links: [ { to: '/docs/manual/basics/', label: 'Language basics' }, { to: '/docs/manual/python/', label: 'Python interop' }, { to: '/docs/manual/values/ownership/', label: 'Ownership' }, { to: '/docs/manual/values/lifetimes/', label: 'Origins and references' }, { to: '/docs/manual/metaprogramming/', label: 'Metaprogramming' }, { to: 'https://max.modular.com/gpu/intro-tutorial/', label: 'GPU programming' }, { to: 'https://puzzles.modular.com/', label: 'GPU Puzzles' }, ], }, { icon: , title: 'Tools & workflow', links: [ { to: '/docs/tools/skills/', label: 'Agent skills' }, { to: '/docs/tools/debugging/', label: 'Debugging' }, { to: '/docs/tools/compilation/', label: 'Compilation targets' }, { to: '/docs/tools/testing/', label: 'Testing' }, { to: '/docs/tools/notebooks/', label: 'Jupyter notebooks' }, ], }, { icon: , title: 'Project info', links: [ { to: '/docs/vision/', label: 'Mojo vision' }, { to: '/docs/roadmap/', label: 'Mojo roadmap' }, { to: '/docs/faq/', label: 'FAQ' }, { to: '/docs/api-docs/stability/', label: 'Stability policy' }, { to: 'https://github.com/modular/modular', label: 'Open source' }, ], }, ]; --- ## Pixi basics Pixi is a CLI tool [from Prefix.dev](https://prefix.dev/blog/launching_pixi) that we recommend you use to manage your package dependencies and virtual environments when developing with the Modular Platform and Mojo language. We like Pixi so much, we created a fork called Magic, but [Magic is now deprecated](https://forum.modular.com/t/migrating-from-magic-to-pixi/1530) because Pixi can do everything we need. So we created this page to help you get started with Pixi. For more details, see [the official Pixi docs](https://pixi.sh/latest/). :::note All our [GitHub code examples](https://github.com/modular/modular/tree/mojo/v1.1.0/Mojo/examples) include a `pixi.toml` file. This file configures the environment to make sure we all use the same packages and get the same results—you just need to install `pixi`. ::: ## Install Pixi You can install Pixi with this command: ```sh curl -fsSL https://pixi.sh/install.sh | bash ``` Then enable auto-completion: ```sh eval "$(pixi completion --shell bash)" ``` ```sh autoload -Uz compinit && compinit # redundant with Oh My Zsh eval "$(pixi completion --shell zsh)" ``` ```sh pixi completion --shell fish | source ``` If you're using a different terminal, see more options in [the Pixi docs](https://pixi.sh/latest/installation/#autocompletion). You can also update `pixi` with this command: ```sh pixi self-update ``` For version information, see the [Pixi changelog](https://pixi.sh/latest/CHANGELOG/). :::caution If you've used a package manager like brew, mamba, conda, paru etc. to install pixi you must use the built-in update mechanism. For example: ```sh brew upgrade pixi ``` ::: ## Create a project and virtual environment You can create a project with its own packages and virtual environment using [`pixi init`](https://pixi.sh/latest/reference/cli/pixi/init/). Normally, you must use the [`--channel`](https://pixi.sh/latest/reference/cli/pixi/init/#arg---channel) argument to specify where to get packages. Instead, we recommend you set default channels in your [user config file](https://pixi.sh/dev/reference/pixi_configuration/) (`$HOME/.pixi/config.toml`). For example, here's how to add the Modular and conda-forge channels as defaults: ```sh mkdir -p $HOME/.pixi echo 'default-channels = ["https://conda.modular.com/max-nightly", "conda-forge"]' \ >> $HOME/.pixi/config.toml ``` Now those channels are always included in your `pixi.toml` when you create a project: ```sh pixi init example-project ``` Then, enter the project directory to install `mojo`: ```sh cd example-project ``` ```sh pixi add mojo ``` Check the installed version: ```sh pixi run mojo --version ``` ## Manage packages Every Pixi project defines its own package dependencies in the local [`pixi.toml`](https://pixi.sh/latest/reference/pixi_manifest/) file. When you run [`pixi add`](https://pixi.sh/latest/reference/cli/pixi/add/) from the project directory, it adds that package name to your `pixi.toml` dependencies. You can optionally specify the version with a [version specifier](https://packaging.python.org/en/latest/specifications/version-specifiers/#id5): ```sh pixi add "mojo~=1.0.0" "numpy<2.0" ``` If you always want the latest version, you can use the wildcard specifier: ```sh pixi add "mojo=*" ``` To update a package, use [`pixi update`](https://pixi.sh/latest/reference/cli/pixi/update/): ```sh pixi update mojo ``` This updates the package if there's a new version that adheres to the version you defined (via `pixi add` and as shown in the `pixi.toml` file). To remove a package, use [`pixi remove`](https://pixi.sh/latest/reference/cli/pixi/remove/): ```sh pixi remove mojo ``` For more about defining dependencies, read about [Pixi dependency tables](https://pixi.sh/latest/reference/project_configuration/#the-dependencies-tables). ### Specify the Python version Even the Python version is controlled like a package with a [version specifier](https://packaging.python.org/en/latest/specifications/version-specifiers/#id5): ```sh pixi add "python==3.11" ``` ```sh pixi run python --version ``` ```output Python 3.11.0 ``` ## Execute code in the environment When your working directory is in a Pixi project (a directory with a [`pixi.toml`](https://pixi.sh/latest/reference/pixi_manifest/) file), you can run any command inside the environment using [`pixi run`](https://pixi.sh/latest/reference/cli/pixi/run/): ```sh pixi run mojo --version ``` Or, you can open a shell in the environment to run your commands: ```sh pixi shell ``` ```sh mojo --version ``` Be sure to exit the shell when you're done: ```sh exit ``` ## Clean up environments You can remove Pixi environments using [`pixi clean`](https://pixi.sh/dev/workspace/environment/#cleaning-up): ```sh pixi clean ``` This removes everything in the Pixi environment, including cached data and built packages. This is particularly useful when iterating on graph builds, as it will clear the MEF cache from the graph compiler. :::caution `pixi clean` removes more than just the MEF cache—it removes the entire Pixi environment. You'll need to reinstall packages after running this command. ::: ## The `pixi.lock` file Although the project manifest file ([`pixi.toml`](https://pixi.sh/latest/reference/pixi_manifest/)) defines your project dependencies, it doesn't define the transitive dependencies (the dependencies of your dependencies). Nor does it always specify the exact version that is installed (such as when you [specify a version](https://packaging.python.org/en/latest/specifications/version-specifiers/#id5) as less-than `<` or greater-than `>`). The transitive dependencies and the actually-installed versions are specified in the [`pixi.lock`](https://pixi.sh/latest/workspace/lockfile/) file, which is automatically generated—you should not edit this file by hand. This file is crucial to ensure that you can reliably reproduce your environment across different machines. You can learn more about it from the [Pixi lock file docs](https://pixi.sh/latest/features/lockfile/). ## More reading There's a whole lot more you can do with Pixi, such as create multi-step [tasks](https://pixi.sh/latest/workspace/advanced_tasks/), install [global tools](https://pixi.sh/latest/global_tools/introduction/), define [multiple environments](https://pixi.sh/latest/workspace/multi_environment/), and more. For more information, see [official Pixi docs](https://pixi.sh/latest/) and [pixi CLI reference](https://pixi.sh/latest/reference/cli/pixi/), or print the help: ```sh pixi -h ``` --- ## System requirements Mojo runs on Mac, Linux, and Windows (with WSL). You don't need a GPU to program with Mojo—GPU support is optional. For installation instructions, see the [install guide](/install/). ## Operating system - glibc 2.34 or later (for example, Ubuntu 22.04 LTS or later). :::note Officially supported distribution Modular tests Mojo on Ubuntu 22.04 LTS or later. Other Linux distributions that meet the glibc 2.34+ requirement are expected to work but aren't continuously tested. If your system has an older glibc version, run Mojo inside a container with a compatible base image such as Ubuntu 22.04. ::: - macOS Sequoia (15) or later. - Apple silicon (M1–M5 processor). Mojo doesn't natively support Windows. You can use Mojo on [Windows with WSL](https://learn.microsoft.com/en-us/windows/wsl/install) using a compatible version of Ubuntu (see the Linux requirements). ## Hardware {/* rumdl's MD013 reflow strips blank lines inside the nested admonition below; disable it for this section. */} - **CPU:** x86-64-v3 (Haswell-class or newer; CPUs from approximately 2013 onward) or ARM64 Neoverse N1 or newer (for example, AWS Graviton2 and later) on Linux; Apple silicon on macOS. :::note x86-64-v3 CPU instruction sets The [x86-64-v3 microarchitecture level](https://github.com/llvm/llvm-project/blob/main/clang/docs/UsersManual.md#x86) requires AVX, AVX2, BMI1, BMI2, F16C, FMA, LZCNT, MOVBE, and XSAVE instructions. To verify your x86-64 CPU on Linux, run `cat /proc/cpuinfo | grep flags` and confirm those flags are present. This requirement doesn't apply to ARM64 or Apple silicon hosts. ::: - **RAM:** 8 GiB minimum for Mojo development. MAX inference and model serving require significantly more memory, with the exact amount varying by model. For the catalog of supported models, see the [supported models](https://max.modular.com/models/) page; for a specific model, check its Hugging Face page for memory details. - **GPU:** Optional. Mojo supports GPU programming across NVIDIA, AMD, and Apple silicon. See [GPU compatibility](#gpu-compatibility) below for supported hardware and driver requirements. ## Software - A C compiler (such as `cc`, `gcc`, or `clang`) on Linux—used as a linker. - Xcode or Xcode Command Line Tools 16 or later on macOS. ## GPU compatibility Mojo supports GPU programming across NVIDIA, AMD, and Apple silicon GPUs. This section covers which GPUs work with Mojo and the software each vendor requires. We categorize GPU compatibility into two levels: - **Continuously tested:** Run in Modular's CI on every release. High confidence that GPU code compiles and executes correctly. - **Known compatible:** Confirmed to work by Modular or community members, but not continuously tested. These GPUs share an architecture with a tested GPU and should work without issues. ### NVIDIA GPUs #### Software requirements - NVIDIA GPU driver 580 or later Check your driver version with [`nvidia-smi`](https://developer.nvidia.com/system-management-interface). To update, see the [NVIDIA driver docs](https://www.nvidia.com/en-us/drivers/). - **Older drivers:** If you're using an NVIDIA driver older than 580 (common on some cloud providers), set the `MODULAR_NVPTX_COMPILER_PATH` environment variable to point to a system `ptxas` binary from a CUDA Toolkit installation: ```bash export MODULAR_NVPTX_COMPILER_PATH=/usr/local/cuda/bin/ptxas ``` This bypasses the bundled compiler's driver version check and enables Mojo to compile GPU code with your existing driver. #### Hardware compatibility GPU Architecture Arch target Continuously tested B200 Blackwell sm_100 Known compatible B300 Blackwell sm_103 B100 Blackwell sm_100 DGX Spark Blackwell sm_121 H200 Hopper sm_90 H100 Hopper sm_90 L4 Ada Lovelace sm_89 L40 Ada Lovelace sm_89 RTX 50XX series Blackwell sm_120 RTX 40XX series Ada Lovelace sm_89 A100 Ampere sm_80 A10 Ampere sm_86 A1000 Ampere sm_86 RTX 30XX series Ampere sm_86 Jetson Orin / Orin Nano Ampere sm_87 Jetson Thor Blackwell sm_110 T4 Turing sm_75 RTX 20XX series Turing sm_75 **Pre-Turing NVIDIA GPUs:** Pre-Turing GPUs (such as Pascal-generation GTX 10XX and Tesla P100) are not supported out of the box. To use Mojo on these GPUs, set the `MODULAR_NVPTX_COMPILER_PATH` environment variable to point to a system `ptxas` binary compatible with your hardware and driver version. ### AMD GPUs #### Software requirements - AMD GPU driver 6.3.3 or later (MI355X requires ROCm 7.0 or later) - For data center GPUs (Instinct series), see the [Ubuntu native install guide](https://rocm.docs.amd.com/projects/install-on-linux/en/latest/install/install-methods/package-manager/package-manager-ubuntu.html). - For Radeon GPUs on Ubuntu, see the [Linux install guide for Radeon software](https://rocm.docs.amd.com/projects/radeon/en/latest/docs/install/native_linux/install-radeon.html). - For Radeon GPUs on WSL, see the [WSL install guide for Radeon software](https://rocm.docs.amd.com/projects/radeon/en/latest/docs/install/wsl/install-radeon.html). #### Hardware compatibility GPU Architecture Arch target Continuously tested MI355X CDNA4 gfx950 MI300X CDNA3 gfx942 Known compatible MI325X CDNA3 gfx942 MI250X CDNA2 gfx90a Radeon RX 9070 RDNA4 gfx1201 Radeon RX 9060 RDNA4 gfx1200 Radeon 880M / 890M RDNA3.5 gfx1150 Radeon 860M RDNA3.5 gfx1152 Radeon 8060S RDNA3.5 gfx1151 Radeon RX 7900 RDNA3 gfx1100 Radeon RX 7800 / 7700 RDNA3 gfx1101 Radeon RX 7600 RDNA3 gfx1102 Radeon 780M RDNA3 gfx1103 Radeon RX 6900 RDNA2 gfx1030 Van Gogh (Steam Deck) RDNA2 gfx1033 ### Apple silicon GPUs #### Software requirements - macOS Sequoia (15) or later - Xcode 16 or later - You may need to install the Metal toolchain after upgrading macOS or Xcode: ```bash xcodebuild -downloadComponent MetalToolchain ``` #### Hardware compatibility Chip Support level M5 Known compatible M4 Known compatible M3 Known compatible M2 Known compatible M1 Known compatible ## Troubleshooting GPU detection ### What Mojo looks for When Mojo needs a GPU, the runtime attempts to load vendor-specific driver libraries in this order: 1. **NVIDIA** (CUDA): loads `libcuda.so.1` and `libnvidia-ml.so.1` 2. **AMD** (HIP): loads `libamdhip64.so` 3. **Apple** (Metal): uses the Metal framework (built into macOS) If Mojo can't load any of these libraries, it falls back to CPU execution. ### Verifying GPU access Use the `--target-accelerator` flag to compile for a specific GPU architecture: ```bash mojo build --target-accelerator=sm_90 my_kernel.mojo ``` To check that your system's GPU is accessible at runtime, try a minimal GPU program: ```mojo title="check_gpu.mojo" from max.gpu.host import DeviceContext def main() raises: var ctx = DeviceContext() print("GPU:", ctx.name()) ``` ```bash mojo check_gpu.mojo ``` If Mojo can't find a GPU, this raises an error explaining what it tried. ### Common issues **GPU not detected despite `nvidia-smi` showing a GPU:** - Verify that the NVIDIA driver version is 580 or later (`nvidia-smi` shows the version in the top-right corner). If your driver is older, set `MODULAR_NVPTX_COMPILER_PATH` as described in the [NVIDIA software requirements](#software-requirements) section above. - Ensure `libcuda.so.1` is on your library path. On most systems, the NVIDIA driver installer places it in `/usr/lib/x86_64-linux-gnu/` or `/usr/lib64/`. - If using a container, make sure you're passing GPU access through (for example, `docker run --gpus all`). - Set `MLRT_CUDA_DEBUG=1` to get detailed logging of the CUDA detection process. **AMD GPU not detected:** - Verify that you have ROCm/HIP installed and that `libamdhip64.so` is available. - Check your ROCm version: `apt show rocm-libs 2>/dev/null | grep Version` or `rocm-smi --showdriverversion`. - Ensure your user is in the `render` and `video` groups: `sudo usermod -aG render,video $USER` (then log out and back in). **Apple GPU not detected or Metal toolchain errors:** - Ensure you're on macOS Sequoia (15) or later: `sw_vers`. - Install the Metal toolchain: `xcodebuild -downloadComponent MetalToolchain`. - Verify that you have Xcode Command Line Tools: `xcode-select --install`. **Pre-Turing NVIDIA GPU errors:** - Set `MODULAR_NVPTX_COMPILER_PATH` to a compatible system `ptxas` binary. For example: `export MODULAR_NVPTX_COMPILER_PATH=/usr/local/cuda/bin/ptxas`. **WSL: GPU not detected or driver mismatch:** - Make sure you follow the WSL-specific installation instructions for your GPU vendor, not the native Linux instructions. - For NVIDIA on WSL, the GPU driver should be installed on the Windows host, not inside WSL. WSL automatically makes the host driver available. - For AMD on WSL, see the [WSL install guide for Radeon software](https://rocm.docs.amd.com/projects/radeon/en/latest/docs/install/wsl/install-radeon.html). --- If your GPU works with Mojo but isn't listed here, let us know on the [Modular community forum](https://forum.modular.com) or [file a GitHub issue](https://github.com/modular/modular/issues). --- ## Mojo roadmap This page provides a high-level roadmap of how we expect the Mojo programming language to evolve over a series of phases. It offers **directional guidance** (not an engineering plan) and is **subject to change**. As we build, learn, and expand Mojo's use cases, we'll iterate, adapt, and invest wherever necessary to unblock priorities. We also periodically share roadmap updates in the [Modular forum announcements section](https://forum.modular.com/c/modular/announcements/9). :::note Phases The phases below are conceptual groups of work—not version numbers—and they have no timeline for completion. Mojo now has versioned releases, but these phases remain roadmap categories rather than release commitments. The status markers below describe our current direction and may change as the language and standard library evolve. ::: :::caution Work items An empty box ⬜ indicates work that's **not started**, a barricade 🚧 is for work **in progress**, and a checked box ✅ means it's **done**. However, this isn't an exhaustive list of work, and the status might be out of date. Also, the items in each list aren't necessarily ordered by priority, and some items may be "nice to have" rather than required to complete the phase. ::: [Jump to current phase](#phase-2) ## Mojo's north star As described in the [Mojo vision](/docs/vision/), we created Mojo to unite developers with a single language that provides ergonomic programming features for accelerator hardware and that scales to solve other challenges in AI and systems programming. Our goals are ambitious, and we're taking on challenges that many languages and systems have struggled with for decades. We believe Mojo has the right blend of **technology, design principles, and community-first philosophy** to succeed, but it'll work only if we stay focused and make deliberate tradeoffs aligned with our long-term vision. The stakes are high—programming languages that succeed shape entire ecosystems, support millions of developers, and define how software is built. That means we must **resist the urge to chase short-term wins** at the expense of long-term clarity, consistency, and quality. Our approach is to keep short-term development focused and anchored on measurable outcomes, while building for generality so Mojo can eventually become a general-purpose language spanning CPUs, GPUs, and other hardware, addressing a myriad of applications. We know the path won't be perfect and we'll make mistakes. But with a strong foundation and an engaged, thoughtful community, we can **learn, iterate, and improve together**. ## Phase 0: Initial bring-up [Jump to current phase](#phase-2) Phase 0 focused on foundational language work: implementing the core parser, defining memory types, functions, structs, initializers, argument conventions, and more. As development accelerated, multiple libraries emerged to fill immediate needs, often overlapping in functionality (for example, multiple pointer types). As the language stabilized, we consolidated these libraries into a coherent and consistent foundation. ## Phase 1: High-performance CPU + accelerator coding [Jump to current phase](#phase-2) Phase 1 took Mojo from a "prototype kernel DSL" to a viable foundation for systems programming and accelerated compute workloads. This phase focused on making Mojo a powerful and expressive language for writing high-performance kernels on CPUs, GPUs, and ASICs, as well as unlocking other performance use cases for CPUs, particularly the ability to extend Python packages in a seamless way. But performance alone isn't enough. We're equally focused on: - **Expressiveness** for building robust libraries - **Good error messages** for developer productivity - **Fast compile times** to support iteration speed We wrapped up phase 1 by **open sourcing the Mojo compiler**. ### Parameterized types and metaprogramming features The backbone of phase 1 is Mojo's **metaprogramming system**, combined with a **modern parameterized type system** that catches errors at compile time, before code is instantiated. - ✅ **Compile-time constructs**: Built out the parameter system, compile-time interpreter, `comptime if` and `for` loops, etc. - ✅ **Predictable dependent types**: Supported advanced parametric algorithms while avoiding rebinding. - ✅ **Parametric comptime values**: Implemented computed parametric values, going beyond types and functions. - ✅ **Traits**: Added traits letting you group types by shared behavior so you can write generic code that works for any conforming type. - ✅ **Trait compositions**: Allowed `Copyable & Defaultable` intersections for precise trait conformance. - ✅ **Default trait methods**: Enable static composition (mixin-style). - ✅ **Parametric raises**: Added the ability to throw types other than `Error` and therefore support higher-order functions like `map()` that propagate the "raisability" of their closure argument. - ✅ **Closure refinement**: Unified representation for compile-time/runtime closures. - ✅ **`where` clauses**: Enabled early constraint checking and better error messages for parameterized declarations. - ✅ **Conditional conformance**: Allowed trait conformance based on predicates over parameters. ### Python interoperability We want Mojo to be an approachable way to extend and speed up existing Python code. We've used the key features of popular libraries like "nanobind" as a guideline: - ✅ **Build integration**: Added seamless connection between Mojo's build system and Python packaging. - ✅ **Python export**: Supported exposing functions and initializers to Python. ### Core language usability and ergonomics Mojo should "just work" for core programming tasks, while offering the control systems programmers expect: - ✅ **Basic constructs**: Built out functions, structs, control flow (`if`, `for`, etc.). - ✅ **Literal support**: Supported infinite-precision integer and floating-point literals, collection literals, and comprehensions. - ✅ **Collections**: Rounded out core types like `List`, `Dict`, `Iterator`, `SIMD`, `String`, etc., to use the language feature set of phase 1. - ✅ **Unsafe programming**: Refined `Pointer` and low-level primitives. - ✅ **Variadic args**: Added support for `*args`, `**kwargs`. - ✅ **Lambda syntax**: Added lambdas for inline closure declarations. - ✅ **Explicitly destroyed (linear) types**: Created types that require an explicit call to deinitialize a value at end of its lifetime. - ✅ **Stabilization markers**: Added mechanism to tag standard library APIs with maturity levels. - 🚧 **Mojo toolchain**: Created Mojo LSP and the VS Code extension, some testing and benchmarking infrastructure, and an LLDB-based debugger, but there's much more to do in phase 2. - ✅ **GPU programmability abstractions**: Built rich and easy-to-use abstractions for a tensor type, data layout, and basic algorithms. ### Syntax and surface language polish These may seem small, but they significantly impact developer ergonomics and reduce future source incompatibilities: - ✅ **Argument conventions**: Refined lifecycle behaviors, convention naming, and default conventions. - ✅ **Literal refinements**: Improved reliability of infinite-precision literals using dependent types. - 🚧 **Attribute macros**: Replaced ad-hoc constructs like `@__parameter`, `@value`, etc., using traits and other existing language features (shrinking the language), but there's still more work to do here. ### Non-goals We intentionally *didn't* pursue the following in phase 1: - **Syntax sugar**: We deferred most sugar until the core language was stable and composable. - **Untyped Python-style code**: For now, Mojo requires explicit `PythonObject` type annotations. - **Python library parity**: We've focused on getting our core language and library abstractions right, rather than expanding coverage. ## Phase 2: Systems application programming {#phase-2} Now that the core parameterized type system and systems programming features have converged and stabilized, we'll begin expanding Mojo to support application-level programming—the kinds of problems that languages like Rust and C++ typically address. That said, **we are not aiming to match Rust or C++ feature-for-feature**. Our goal is to keep Mojo a relatively small and teachable language—one that solves specific problems while maintaining a focus on [composability and simplicity](/docs/vision#managing-language-complexity). If phase 1 built a strong foundation, the major theme of phase 2 is *deepening* our core investment in systems programming and heterogeneous hardware, while *broadening* our ecosystem by supporting new use cases, from servers and networking code to microcontrollers and robotics. ### Language features - ⬜ **First-class `async` support**: Fully integrated with Mojo's type and memory models. - ⬜ **Existentials / dynamic traits**: For building flexible runtime abstractions. - ⬜ **Richer metatypes**: Extending support for type-level programming. - ⬜ **Struct extensions**: Post-hoc type extension and better modular refactoring. See the [struct extension proposal](https://github.com/modular/modular/blob/mojo/v1.1.0/Mojo/proposals/struct-extensions.md). - ⬜ **Algebraic data types & pattern matching**: Enabling expressive state modeling. - ⬜ **Dynamic reflection features**: To complement Mojo's powerful compile-time reflection support. - ⬜ **Initial distributed programming support**. Leveraging Mojo across multiple machines. - ⬜ **Access control features**: For example, `private` modifiers (or formalizing the underscore convention) to prevent violating abstraction boundaries. ### Memory safety model: fast, expressive, gradual Mojo code should be memory-safe by default, while still being fast, expressive, and gradually more complex for beginners. Although phase 1 provides a strong framework for [memory safety](/docs/manual/values/), some situations still aren't safe by default, and we don't expect to resolve them fully until phase 2. This will likely require: - ⬜ **Finalizing internal origins**: Providing memory-safe references to values held in collections. - ⬜ **Supporting mutable aliasing**. Making memory safety more ergonomic. ### Broadening the ecosystem Expanding the ecosystem means meeting more developers where they are, and improving developer productivity with better tools. - ⬜ **Expand platform support**: Supporting new types of hardware. - ⬜ **Keep improving interop**: Interop with other languages is increasingly important. - ⬜ **Packaging and package management**: To enable a vibrant ecosystem of Mojo libraries. [Join our conversation about a package manager](https://forum.modular.com/t/open-question-what-would-you-like-to-see-from-a-mojo-package-manager/2799). - 🚧 **Stable and robust toolchain**: Continue building out Mojo's toolchain: cross-compilation, testing and benchmarking framework in the standard library, debugger, profiler, etc. - 🚧 Mojo Language Server Protocol support and VS Code extension - 🚧 Testing framework - 🚧 Benchmarking framework - 🚧 Debugger - ⬜ Profiler ## Phase 3: Dynamic object-oriented programming Eventually, we want Mojo to support the core dynamic features that make Python great, including untyped variables, classes, inheritance, etc. We have some thoughts about how these features will compose with the language's other features, but defer detailed planning and scoping until the earlier phases are done. As Mojo matures through phase 3, we believe it will become increasingly compatible with Python code and deeply familiar to Python users, but more efficient, powerful, coherent, and safe. Mojo may or may not evolve into a full superset of Python, and it's okay if it doesn't. We're encouraged by how well AI-assisted coding tools already help migrate Python to Mojo today, and we're confident that future tooling and ecosystem maturity will make this evolution even smoother. ## Continuous investments The following topics remain in progress throughout Mojo's lifetime and aren't tied to any specific phase: - **Error messages and diagnostics**: Always room for improvement, but parameter inference and elaborator errors need particular attention. - **Compile times**: We'll continue pushing for faster developer iteration cycles. - **Standard library cleanup**: API consolidation, regularization, and new capabilities. - **Hardware support**: Extending Mojo's backend to support new architectures. ## Contributing to Mojo {#contributing-to-mojo} Mojo is fully open source on GitHub, and we're accepting contributions in several areas, including the compiler, the standard library, and docs. Each team accepts a specific set of changes, so check the [contribution areas](/community/contributing/contribution-areas/) before you start work. You can learn more about contributing to Mojo from the [Mojo contributor guide](/community/contributing/). If you encounter any bugs with Mojo, please [submit an issue on GitHub](https://github.com/modular/modular/issues). --- ## Mojo vision As the world rapidly scales the amount of available compute to support the growing AI demand, the available hardware is increasingly heterogeneous. The compute spans datacenters and client devices that are filled with chips from different vendors, each with their own software stack. Our vision is to unify this fragmented software stack with the one programming language developers can use to target all the diverse hardware—CPUs, GPUs, custom accelerators, ASICs, and more. Mojo is already able to target CPUs and GPUs from different vendors, making it the first language built for the AI era, but to be the one systems programming language for all hardware, there's a lot of work left to do. This document explains more about our motives and aspirations for the Mojo language. This serves as a baseline to guide our decision-making—it's a "directional" vision, not an engineering plan. For a look at some of the planned work, instead see the [Mojo roadmap](/docs/roadmap/). ## Mojo's role in Modular's mission Mojo plays a key role in Modular's mission to [democratize AI compute](https://www.modular.com/democratizing-ai-compute). Let's break down the mission into its component parts: - **Democratize**: This is a social statement, saying that we want to free, unlock, and enable more people to participate. - **AI compute**: We have long passed the end of Moore's law, and are awash with a wide range of accelerators: GPUs, TPUs, and accelerated CPUs, spanning IoT, edge, client, datacenter, and supercomputer applications. (Our ambition is to eventually expand Mojo into "all compute.") Mojo is how we bring these two ideas into a single, coherent solution. To achieve this we want to: - **Unite developers** across domains, skill levels, and backgrounds by solving the complexity of juggling Python, C++, Rust, CUDA, and more (the "N language problem"). - **Unify hardware** by giving developers a consistent set of tools to access the capabilities of any hardware—CPUs, GPUs, custom accelerators, ASICs, and more. It should be easy to start using Mojo, and then incrementally adopt features that deliver more performance and scale beyond CPUs into other hardware. This mission is vast and ambitious, but when done right, we believe Mojo can unlock creativity, productivity, and applications we haven't yet imagined. ## Why Mojo was built from scratch Modern accelerators are complex and very different from traditional CPUs. They have features like Tensor Cores, systolic arrays, dedicated convolutional units, explicit memory hierarchies, memory transfer accelerators, and a variety of exotic and rapidly evolving data types like float6. Achieving our mission to unify hardware development means Mojo must deliver the full performance potential of any given accelerator. There are only three ways to tackle these problems. Let's briefly evaluate the pros and cons of each: 1. **Extend an existing language like C++, Rust, Julia, Swift**: - **Pro**: You get an existing implementation and community. - **Con**: None of these languages support the hardware features we need—they were designed for CPUs. They are also all 10+ years old, don't provide the modern metaprogramming features we need, and weren't designed to support hardware features required for AI (such as float6). 2. **Create an embedded DSL for a language like Python or C++**: - **Pro**: This is comparatively easy to implement. - **Con**: The tooling, UX, and predictability of these systems are very problematic and they are limited by the base language syntax. This is particularly problematic if you're trying to introduce fundamental new concepts because you can't change the grammar of Python or C++. [More about eDSLs](https://www.modular.com/blog/democratizing-ai-compute-part-7-what-about-triton-and-python-edsls) 3. **Build an entirely new programming language from scratch**: - **Pro**: You get full control to create the best quality result. - **Con**: This is extremely expensive and difficult to do. There are many ways to get this wrong and you must have a strong set of principles to guide development. For comparison, CUDA is a C++ extension and runtime—nothing as ambitious as a new programming language. We ruled out the first two options because they're insufficient for achieving the full scope of our vision. We believe GPUs, TPUs, and other accelerators are the natural evolution of compute going forward and demand high-quality software to achieve their full potential. Therefore, we believe it's worthwhile to bet big, rather than do something easier that might get near-term results but wither away over time as AI and accelerator hardware continues to rapidly evolve. ## Overarching design principles Because Mojo will evolve over time, it's essential to prioritize deliberately—staying focused on our long-term goals while making pragmatic short-term decisions. The following are the high-level design principles that guide Mojo's development. ### Member of the Python family Mojo adopts Python's syntax and should feel familiar to Python developers—Python is not only [one of the most popular programming languages in the world](https://www.tiobe.com/tiobe-index/), but it's also the dominant language in AI. Python is beloved for its clean and readable syntax, small core language (compared to many alternatives), powerful metaprogramming, and its role as a "universal superglue" for integrating complex systems across language boundaries. That's why Mojo supports the core features Python programmers instinctively reach for—`if`/`for` statements, lists, dictionaries, etc.—so it's easy to migrate code. Mojo will support more Python features over time, but our primary focus is on building features that unlock high-performance, portable compute—not on quickly achieving surface-level Python compatibility. ### Scalable AI kernel development A key principle for Mojo is to overcome the fundamental scalability limitations that plague traditional kernel libraries and ML compilers, and become a unified language for kernel development. Kernel libraries, while initially useful, become [hard to manage as systems grow](https://www.modular.com/blog/democratizing-ai-compute-part-5-what-about-cuda-c-alternatives). ML compilers, despite their sophistication, often [lack the generality needed for diverse tasks](https://www.modular.com/blog/democratizing-ai-compute-part-6-what-about-ai-compilers) like data loading, preprocessing, dynamic shapes, and sparsity—they failed to provide an "it just works" experience. Even other [MLIR-based compiler systems](https://www.modular.com/blog/democratizing-ai-compute-part-8-what-about-the-mlir-compiler-infrastructure) failed to solve this due to a fragmented development process that couldn't scale to handle the constantly changing requirements in numerics, data types, AI modeling, and hardware. Thus, while building our inference engine for [MAX](https://www.modular.com/max), we wanted a new way to write kernels that could scale with the ever-evolving AI industry. We took inspiration from kernel programming systems (CUDA, CUTLASS, DSLs, etc.), and built a way to express common kernel development patterns in MLIR. Then we took a step further and generalized those patterns into a new language that's suitable for high-performance kernel development. For example, Mojo includes zero-cost abstractions, knobs that can be tuned for optimal hardware performance, a library-first design, and metaprogramming to allow specialization for particular hardware. ### A modern systems programming language Mojo must address the realities of modern accelerators, which are essentially high-performance embedded systems. For example, you don't want to upload megabytes of code just to run a matrix multiplication, and you can't afford implicit performance overhead in inner loops. That means Mojo must be designed for low-level numerical and hardware-specific programming. Mojo includes systems programming constructs such as static typing, memory management control, and predictable performance semantics. It draws on lessons from languages like Swift, C++, Rust, and Zig—and goes beyond them by embracing new techniques that allow Mojo to support the wide range of hardware that AI developers must face today and in the future. ### Managing language complexity Mojo must add new features aligned with our mission—but in doing so, we face the same scope-creep pressures that every growing language faces eventually. The complexity of some languages (notably C++) has spiraled out of control by adding new features that don't quite fit together. This happens due to a "tragedy of the commons"—every feature is justified by a specific use-case, but all users suffer from the aggregate complexity. Other languages like Go pride themselves on maintaining simplicity and saying "no" to proposals that don't benefit long-term goals. We aim to control complexity through a few specific strategies: 1. **Use Mojo heavily**: Modular is Mojo's largest user and maintains the world's largest Mojo codebase (which is open source). This gives us direct insight into real-world usability and performance. We use our own experience, as well as feedback from our enthusiastic community, to guide prioritization. 2. **Align with Python wherever possible**: If Python already supports a feature, we adopt its design rather than inventing something new. Any deviation from Python requires a strong, mission-driven justification. 3. **Adopt proven ideas from modern languages**: When new features are required (such as static types, traits, metaprogramming), we draw from languages like Rust, Swift, and Zig rather than create novel and untested solutions. 4. **Innovate only when necessary**: Where existing designs fall short—such as ergonomics in Rust or compile-time error messages in Zig—we aim beyond them to meet Mojo's goals. 5. **Emphasize composability and simplicity**: Every Mojo feature must work reliably in all situations and combine seamlessly with other features (compose orthogonally). We're not satisfied with features that work 80% of the time but fail in some cases. 6. **Defer syntactic sugar**: Language sugar is often tempting, but we prioritize core "big rocks" first. Only once the fundamentals are solid do we revisit syntactic enhancements. These are guiding principles, not a rigid recipe. The Mojo team draws on deep experience, learns through continuous implementation and iteration, and listens to feedback from the broader community. ## Architectural bets for Mojo We believe building a new programming model for the future of AI and systems programming requires first-principles thinking, not incremental evolution. That's why our vision for Mojo is built upon a few specific architectural bets, as described in this section. From the start, we made a foundational bet: By uniting three key technologies, we can build a new kind of systems programming architecture that scales across the full range of hardware targets while maximizing software reuse. Thus, Mojo's architecture is built upon the following technologies: 1. Powerful parametric metaprogramming 2. MLIR Core 3. MAX framework integration This design is built on experience, not speculation—before we committed to the language, we spent nearly a year prototyping and validating this approach through deep compiler research and development. Let's explore each of these technologies in more detail. ### Powerful parametric metaprogramming Accelerators are incredibly diverse, and they're constantly evolving. Our goal is to drastically reduce the time and effort required to bring up a software stack for a new chip. We believe the work should be proportional to *how different* that chip is, rather than starting from scratch for every architecture. The core insight behind our approach is this: while no two accelerators are exactly alike, their target workloads and macro-architectures share deep structural similarities. For example, NVIDIA's Hopper architecture extends from Ampere, and AMD's MI300 has meaningful overlap with both. Across the industry, NVIDIA's Tensor Core concept has become ubiquitous, showing up in CPUs, GPUs, and custom ASICs. These units may be quirky in their own ways, but their purpose is the same: efficiently run matrix multiplications. Previous attempts to simplify kernel development often failed to capitalize on this commonality. Many were built on fragmented, vendor-specific libraries like cuBLAS or rocBLAS, which prevented true cross-architecture unification. We made a different bet: we could reimplement and unify these software stacks ourselves—for example, build a graph compiler and runtime stack ([MAX](https://www.modular.com/max)) *without* CUDA—and use that as a basis to abstract across hardware architectures. Of course, this only works if it scales. The challenge is combinatorial: the cross-product of all data types, operators, and hardware targets is too large to implement by hand. That's why Mojo includes powerful [metaprogramming](/docs/manual/metaprogramming/). We took the ideas behind C++ templates (compile-time polymorphism and specialization) and built something dramatically more usable, with better error messages, faster compile times, more expressiveness, and a smoother developer experience. In late 2022, an early prototype of the Mojo parameter system enabled us to implement matrix multiplication in a unified way and match or [exceed vendor BLAS libraries across a range of CPUs](https://www.modular.com/blog/the-worlds-fastest-unified-matrix-multiplication). This architectural bet has paid off many times over—it's what allows MAX to scale across hardware with high performance and maintainable code. ### MLIR Core MLIR is a widely used open-source compiler framework for building domain-specific compilers. It powers systems across AI accelerators, CPUs, hardware design, quantum computing, and more. Within it, you can think of *MLIR Core* as a flexible "compiler construction toolkit," providing the building blocks needed to create powerful custom compilers. :::note The broader MLIR project includes many AI-related dialects, such as `linalg`, `affine`, and `scf`, but Mojo doesn't use any of these—Mojo is built purely on top of MLIR Core. ::: Mojo is powered by a novel compiler framework, historically code-named **KGEN** (for "kernel generator"). KGEN is built using MLIR Core and forms the backbone of Mojo's metaprogramming capabilities. It allows explicitly parametric code to be represented *before* instantiation, which enables a host of benefits: faster compile times, clearer error messages, and support for compiling the same source code to multiple target devices. Another key design choice in Mojo is that it acts as syntactic sugar for MLIR. This means Mojo code can directly express MLIR dialect operations, without modifying the Mojo compiler itself. While not all MLIR dialects are supported, Mojo is designed to cover the most important ones needed for accelerator programming. Broader dialect support is possible in the future, but it's not a near-term priority. For a deep dive into how Mojo uses MLIR and KGEN, see the video, [Modular Tech Talk: Kernel Programming and Mojo](https://www.youtube.com/watch?v=Invd_dxC2RU). ### MAX framework integration Mojo's low-level programming model and MLIR-based foundation make it easy to write high-performance code, but raw kernel performance isn't the whole story. In AI and other advanced domains, some of the biggest gains come from graph-level optimizations like *kernel fusion*. That's why Mojo is designed to integrate seamlessly into the [MAX framework](https://www.modular.com/max)—our graph compiler and runtime stack. This integration allows developers to directly extend MAX using Mojo (for example, write custom graph ops)—without modifying the graph compiler itself—and still benefit from advanced optimizations and code transformations. This is possible because Mojo builds an MLIR representation of the kernel code before instantiating it with parameters. This intermediate representation (IR) allows the graph compiler to reflect over the kernel to understand the inputs and outputs, and transform the kernel's IR directly. As Mojo evolves, a key goal remains enabling and enriching the MAX framework. We want to unlock new forms of optimization and fusion that only become possible when you reason across combinations of kernels—not just individual operators. These kinds of transformations can dramatically improve performance, reduce memory usage, and lower the cost of deploying high-performance AI systems at scale. ## Looking ahead Mojo stands on a carefully engineered foundation—one designed to scale across devices, abstractions, and time. These investments are already paying off, and we believe they position Mojo to grow into a truly transformative technology for the AI era and beyond. Although this document focuses on the Mojo language, we know it's just one part of a larger Mojo ecosystem. When combined, the developer tools, the community, and the landscape of Mojo libraries are arguably more important at scale. We want to build a vibrant ecosystem where people use Mojo to create and share a wide range of applications, servers, large-scale distributed systems, database connectors, and much more—putting the power of the world's compute hardware at your fingertips. That's when we'll feel like Mojo is truly on fire 🔥! We're building it together, and we're **building it to last**. [Join our community](/community/) and help us build the future of Mojo! --- ## Mojo language basics This page provides an overview of the Mojo language. If you know Python, then a lot of Mojo code looks 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](/docs/manual/get-started/) 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 this page links to them as appropriate. Let's get started! 🔥 :::note Mojo is a young language that's still [evolving](/docs/roadmap/). As such, Mojo is currently **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: ```mojo def main(): print("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()`](/docs/std/io/io/print/) function does what you'd expect, printing its arguments to the standard output. This page omits `def main():` for many brief examples. To test these, add them to a `main()` function. ## Variables In Mojo, you can declare a variable using the `var` keyword: ```mojo def main(): var x = 10 var y = x * x print(y) ``` You can also explicitly declare the variable type, with or without an assignment: ```mojo def main(): var x: Int = 10 var sum: Int sum = x + x ``` Mojo variables are statically typed: that is, Mojo sets a variable's type at compile time, and the type doesn't change at runtime. If you don't specify a type, Mojo uses the type of the first value assigned to the variable. ```mojo var x = 10 x = "Foo" # Error: cannot implicitly convert 'StringLiteral["Foo"]' value to 'Int' ``` For more details, see the page about [variables](/docs/manual/variables/). ## Blocks and statements Define code blocks such as functions, conditions, and loops with a colon followed by indented lines. For example: ```mojo 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. The Mojo compiler is fairly lenient in allowing extra line breaks. As a rule of thumb, you can always break statements between a pair of parentheses (`()`), square brackets (`[]`), or curly braces (`{}`): ```mojo matrix_multiply( matrix_a, matrix_b, result_matrix ) ``` You can add parentheses to continue a statement across lines: ```mojo var 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." ) ``` Mojo combines adjacent string literals, so `long_text` ends up with a single, combined string. For more information on loops and conditional statements, see [Control flow](/docs/manual/control-flow/). ## Functions 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`](/docs/std/collections/string/string/String/) argument and returns a `String`: ```mojo def greet(name: String) -> String: return "Hello, " + name + "!" ``` ## Code comments You can create a one-line comment using the hash `#` symbol: ```mojo # This is a comment. The Mojo compiler ignores this line. ``` Comments may also follow some code: ```mojo var message = "Hello, World!" # This is also a valid comment ``` Enclose API documentation comments in triple quotes. For example: ```mojo 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 [`mojo doc` command](/docs/cli/doc/). :::note Technically, docstrings aren't _comments_, they're a special use of Mojo's syntax for multi-line string literals. For details, see [String literals](/docs/manual/types/#string-literals) in the page on [Types](/docs/manual/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—the compiler binds them at compile time, so they don't 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: ```mojo 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: ```mojo def use_mypair(): var mine = MyPair(2, 4) mine.dump() ``` The `MyPair` struct contains two special methods, `__init__()`, the initializer, and `__init__(out self, *, copy: Self)`, the copy initializer. _Lifecycle methods_ like this control how Mojo creates, copies, moves, and destroys a struct. For most simple types, you don't need to write the lifecycle methods. You can use the [`@fieldwise_init`](/docs/reference/decorators/fieldwise-init/) decorator to generate the boilerplate field-wise initializer for you, and Mojo synthesizes copy and move initializers if you ask for them with trait conformance. So you can simplify the `MyPair` struct to this: ```mojo @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](/docs/manual/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 "conforms" to the trait. Using traits allows you to write parameterized functions that can accept any type that conforms to a trait, rather than accepting only specific types. For example, here's how you can create a trait: ```mojo trait SomeTrait: def required_method(self, x: Int): ... ``` The three dots following the method signature are Mojo syntax indicating that the method has no implementation. Here's a struct that conforms to `SomeTrait`: ```mojo @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): ```mojo 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`](/docs/std/collections/list/List/) and [`Dict`](/docs/std/collections/dict/Dict/) can store any type that conforms to the [`Movable`](/docs/std/traits/movable/Movable/) trait (`Dict` keys must also conform to [`KeyElement`](/docs/std/collections/dict/#keyelement)). You can specify the type when you create a collection: ```mojo var my_list = List[Float64]() ``` :::note You're probably wondering about the square brackets on `fun_with_traits()`. These aren't function _arguments_ (which go in parentheses); these are compile-time _parameters_, which we'll explain in the next section. ::: 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 a "parameterized function" because it accepts a _generalized_ type instead of a specific type. For more details, see the page about [traits](/docs/manual/traits/). ## Parameterization In Mojo, a parameter is a compile-time variable that becomes a runtime constant, and you declare it 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 "parameterized function," we're talking about these compile-time parameters. In contrast, a function "argument" is a runtime value that you declare in parentheses. Parameterization is a complex topic that the [Metaprogramming](/docs/manual/metaprogramming/) section covers in much more detail, but we want to break the ice just a little bit here. To get you started, let's look at a parameterized function: ```mojo 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`](/docs/std/simd/#int) and one argument of type `String`. To call the function, you need to specify both the parameter and the argument: ```mojo def call_repeat(): repeat[3]("Hello") # Prints "Hello" 3 times ``` By specifying `count` as a parameter, the Mojo compiler can optimize the function because this value can't 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](/docs/manual/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: ```mojo 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](/docs/manual/python/). ## 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](/docs/manual/functions/). Otherwise, here are some other resources to check out: - See [Get started with Mojo](/docs/manual/get-started/) for a hands-on tutorial that gets you up and running with Mojo. - If you want to experiment with some code, clone [our GitHub repo](https://github.com/modular/modular/) to try our code examples: ```sh git clone https://github.com/modular/modular.git cd modular/Mojo/examples ``` - To see all the available Mojo APIs, check out the [Mojo standard library reference](/docs/std/). --- ## Using Mojo's C foreign function interface to call C libraries When you need functionality that's already available in a C library, you can call it directly from your Mojo code. Many libraries for graphics, databases, hardware control, signal processing, and scientific computing expose C APIs. Mojo emits a direct native call, with no translation layer or extra runtime overhead. A C call from Mojo runs as fast as handwritten C. ## C number types C integer types don't have fixed sizes. Their sizes depend on the target platform and its C *ABI*. For example, `int` is commonly 32 bits, while `long` is 64 bits on Linux and macOS but 32 bits on Windows. An ABI (application binary interface) defines how machine code passes arguments, returns values, and lays out data in memory. Use the `std.ffi` module's type aliases when working with C APIs. They match the target platform's C ABI, so you don't need to worry about platform-specific size differences. See the [C type reference](#c-type-reference) at the end of this page for a list of `std.ffi` type aliases and their equivalent Mojo types. ## Call libc functions `libc` is the C standard library. It provides functions for memory allocation, string manipulation, file I/O, and other common tasks. Mojo calls libc functions with `external_call()`. Mojo resolves the symbol for you, so you don't need to add anything to your build. Import `external_call` from `std.ffi`. Parameterize it with the function name and return type. Then pass the function arguments in parentheses. Mojo infers the argument types from the values you pass, so there's nothing else to declare: ```mojo def external_call[ callee: StaticString, return_type: RegisterPassable, *types: AnyType, num_fixed_args: OptionalReg[Int] = None, ](*args: *types) -> return_type ``` The following example calls the C `abs()` function, which returns the absolute value of an integer: ```mojo from std.ffi import external_call, c_int def main(): # int abs(int n); var n = external_call["abs", c_int](c_int(-42)) print(t"Absolute value is 42: {n == 42}") # True ``` `c_int` is the `std.ffi` alias for C's `int`. It's 32 bits on every platform Mojo targets. Its Mojo counterpart is `Int32`. ### Call variadic C functions A *variadic* C function takes a variable number of arguments, like `printf()` and `snprintf()`. Pass `num_fixed_args` with the number of arguments declared before the `...`: ```mojo from std.ffi import external_call, c_char, c_int, c_size_t def main(): # int snprintf(char *buf, size_t size, const char *fmt, ...); # Three fixed arguments, so num_fixed_args=3. var buf = Array[c_char, 64](uninitialized=True) var written = external_call["snprintf", c_int, num_fixed_args=3]( buf.unsafe_ptr(), c_size_t(64), "score: %d/%d".as_c_string_span(), c_int(7), c_int(10), ) print(t"wrote {written}: {String(unsafe_from_utf8_ptr=buf.unsafe_ptr())}") ``` Without `num_fixed_args`, Mojo treats every argument as fixed. Some ABIs pass variadic arguments differently from fixed ones, so the call can work on one target and break on another. ## Use shared libraries An `OwnedDLHandle` owns a handle to a dynamically linked library with RAII semantics. Use it to load shared libraries and retrieve functions as Mojo callables, so you can work with libraries such as SQLite, libcurl, camera SDKs, GPU vendor libraries, and other native libraries. Library names differ by platform, so use `platform_map()` to select the right one at compile time: ```mojo from std.ffi import OwnedDLHandle, c_double from std.sys.info import platform_map comptime LIBM = platform_map["libm", linux="libm.so.6", macos="libm.dylib"]() def main() raises: var lib = OwnedDLHandle(LIBM) var sqrt = lib.get_function[c_double]("sqrt") print(sqrt(c_double(4.0))) # Prints: 2.0 # Library automatically closed when lib goes out of scope ``` If `platform_map()` has no value for the target, it raises a compilation error. It won't fall through to a library name for another platform. ### Library names Pass the library as any `os.PathLike`, such as a `String` or a `Path`. Mojo resolves the name at runtime. Use the bare name (`libm.dylib`) when the library is on the system search path, or a full path (`path/to/libm.dylib`) when it isn't. On Linux, use the ABI-versioned runtime name, such as `libm.so.6`, instead of the unversioned `libm.so`. An ABI version doesn't necessarily match the library's release version. For example, libcurl 8.21 still uses `libcurl.so.4`. The unversioned name belongs to the development package, where the static linker consumes it for options such as `-lm`. It's often a linker script rather than a library, so passing it to `dlopen` can fail with an `invalid ELF header` error. Find the shared libraries the dynamic linker knows about with: ```bash ldconfig -p | grep libcurl ``` macOS uses one name for both purposes. `libcurl.dylib` is both what you link against and what you load. If you omit the library name, `OwnedDLHandle()` opens the current process. This is another way to call libc functions and other symbols already linked into your program. ### Availability checks `OwnedDLHandle` loads libraries at runtime, so the library must be available when your program runs. If it can't be found, loading fails: ```mojo comptime LIBCURL = platform_map[ "libcurl", linux="libcurl.so.4", macos="libcurl.dylib" ]() try: var lib = OwnedDLHandle(LIBCURL) # use the optional feature except: # fall back ``` You can guard against missing functions with `check_symbol()`. Use it to test for optional, versioned, or platform-specific features. The check works for both functions and exported globals: ```mojo comptime LIBM = platform_map[ "libm", linux="libm.so.6", macos="libm.dylib" ]() var lib = OwnedDLHandle(LIBM) if lib.check_symbol("exp10"): var exp10 = lib.get_function[c_double]("exp10") print(exp10(c_double(2.0))) # 100.0 else: print("exp10 not found in libm") ``` ### Retrieve functions by name `get_function()` looks up a library function by name and returns a callable. Parameterize it with the C function's return type. Here's a curses example: ```mojo # WinPtr is a pointer to a curses window struct var wgetch = lib.get_function[c_int]("wgetch") # ... later _ = wgetch(win) # blocks until a key is pressed. ``` You don't declare the argument types. Mojo infers them from the values you pass at each call, and forwards them using the C calling convention. Missing symbols raise errors. ## Passing pointers {#pointers} Many C APIs work with pointers. Mojo represents raw pointers with `Pointer[T]`, where `T` is the pointed-to type. When a C API expects a `void*`, use `.unsafe_bitcast[NoneType]()` to produce an `OpaquePointer`. - Use `Pointer(to=value)` to get a pointer to a Mojo value. - Use `.unsafe_bitcast[U]()` to reinterpret a pointer as another pointer type. For example: ```mojo var value: c_int = 42 var p = Pointer(to=value) # Pointer to a C int var opaque: OpaquePointer[origin_of(value)] = p.unsafe_bitcast[NoneType]() ``` ### Typed pointers C functions often write results through a pointer you provide, rather than returning them. Pass `Pointer(to=value)` and C fills in the value. An `imm` function argument won't work, and, worse, it fails quietly, leaving the value unchanged. Use the `mut` convention or copy the value into a local `var` before your call. This example passes a Mojo floating-point number to C's `frexp`, which splits it into a mantissa and an exponent: ```mojo from std.ffi import external_call, c_double, c_int def main(): # double frexp(double x, int *exp); # Returns the mantissa and writes the exponent through the pointer. var exponent: c_int = 0 var mantissa = external_call["frexp", c_double]( c_double(12.0), Pointer(to=exponent) ) print(t"12.0 = {mantissa} * 2^{exponent}") # 0.75 * 2^4 ``` ### Opaque pointers {#opaque-pointers} The C standard library provides `qsort`, a general-purpose sorting function. `qsort` sorts its array in place. You provide a pointer to that array, its number of elements, the element size, and a comparison function. Whenever `qsort` compares two elements, it calls your Mojo-native comparison function. The comparison function must be *thin*. That is, it can't capture any Mojo state as a closure. You must mark it with `abi("C")`, allowing `qsort` to call it across the FFI boundary. The following example sorts a list of C integers. The `compare()` function receives two `void*` pointers, casts them back to `c_int*`, and returns the comparison result: ```mojo from std.ffi import external_call, c_int, c_size_t from std.sys import size_of def compare( a: OpaquePointer[mut=False, _], b: OpaquePointer[mut=False, _], ) abi("C") -> c_int: var a_value = a.unsafe_bitcast[c_int]()[] var b_value = b.unsafe_bitcast[c_int]()[] # `qsort` only needs to know which value is larger. Compare the values # instead of subtracting them. Large differences can overflow, producing # the wrong comparison result and sorting the values incorrectly. if a_value < b_value: return c_int(-1) return c_int(a_value > b_value) def main() raises: var numbers: List[c_int] = [5, 2, 9, 1, 5, 6] var count = c_size_t(len(numbers)) var size = c_size_t(size_of[c_int]()) external_call["qsort", NoneType]( numbers.unsafe_ptr(), count, size, compare, ) print("Sorted numbers:", numbers) # [1, 2, 5, 5, 6, 9] ``` ## Passing structs Struct pointers allow Mojo and C APIs to exchange structured data that goes beyond simple values. For example, `clock_gettime()` writes the system's monotonic time into a C `struct timespec`. To read that data from Mojo, define a struct with a C-compatible layout and pass a pointer to it: ```mojo from std.ffi import external_call, c_int, c_long from std.sys.info import platform_map @fieldwise_init struct CTimeSpec(RegisterPassable): # Matches C's struct timespec. # CLOCK_MONOTONIC differs by platform comptime monotonic = c_int( platform_map["CLOCK_MONOTONIC", linux=1, macos=6]() ) var tv_sec: c_long var tv_nsec: c_long @staticmethod def monotonic_nanos() raises -> c_long: var time_spec = Self(0, 0) if ( external_call["clock_gettime", c_int]( Self.monotonic, Pointer(to=time_spec), ) != 0 ): raise Error("clock_gettime failed") return time_spec.tv_sec * 1_000_000_000 + time_spec.tv_nsec def main() raises: print(t"Monotonic time: {CTimeSpec.monotonic_nanos()} ns") ``` ### C-compatible structs C-compatible types are ordinary structs with two requirements: - They conform to `RegisterPassable`. - They contain only C-compatible fields. Field order matters. Declare your fields in the same order as the C struct you're mirroring. Mojo uses the corresponding C layout, including padding required for [field alignment](/docs/reference/decorators/align/): ```mojo # Mirrors C `div_t`: two ints, 8 bytes total. @fieldwise_init struct DivT(RegisterPassable): var quot: c_int var rem: c_int def main() raises: var proc = OwnedDLHandle() # No path: opens the current process var div = proc.get_function[DivT]("div") var d = div(c_int(7), c_int(3)) print(t"div(7, 3): quot {d.quot} rem {d.rem}") # 2 1 ``` ### Passing lists, arrays, and spans A Mojo `List[T]` stores its elements contiguously in memory, just like C arrays. You pass a list to C as a pointer plus a length, as shown in the [`qsort` example](#opaque-pointers). Mojo list pointers are fragile. Operations that grow the list, such as `append()`, may move its storage and leave an earlier pointer stale. So get the pointer fresh, right before you use it, after any change to the list. `Span[T]` is Mojo's built-in pointer-plus-length pair. It wraps a pointer to contiguous memory and stores a length. This gives you built-in bounds checking and safe iteration. `Array[T, length]` is Mojo's fixed-size array. It owns its elements inline, so Mojo cleans it up and C can fill it through a pointer plus a length. Both `Span` and `Array` are safe to pass to and from C by pointer. Add a length to calls where C needs one. The following example allocates a 256-byte `Array`, passes it to C's `getcwd()`, wraps the filled bytes in a `Span`, and converts them to a Mojo `String`: ```mojo from std.ffi import external_call, c_char, c_size_t def main() raises: # char *getcwd(char *buf, size_t size); C fills a buffer that Mojo owns. comptime CAPACITY = 256 var buf = Array[c_char, CAPACITY](uninitialized=True) var filled = external_call[ "getcwd", Optional[Pointer[c_char, origin_of(buf)]] ](buf.unsafe_ptr(), c_size_t(CAPACITY)) if not filled: raise Error("getcwd failed") # C reports no length, so ask for it, then wrap the bytes in a `Span`. var length = external_call["strlen", c_size_t](buf.unsafe_ptr()) var span = Span( unsafe_ptr=buf.unsafe_ptr().unsafe_bitcast[Byte](), length=Int(length) ) print(t"{len(span)} bytes: {String(from_utf8=span)}") ``` `Span`s work with both Mojo and C memory: - If you wrap a Mojo-owned buffer, the `Span` keeps it alive. - If you wrap a C-owned buffer, such as memory from `malloc()`, the `Span` doesn't free it. You must free C-owned memory with C. ## Passing strings C strings are null-terminated byte arrays (`char*`). Mojo strings are length-prefixed UTF-8. ### Convert a Mojo string to a C string Call `as_c_string_span()` on a `String` to ensure null termination, and get a type safe `CStringSpan` back. ```mojo name.as_c_string_span() ``` The source string must be mutable because `as_c_string_span()` may append a terminating zero byte. It may also move the string's buffer, so call it once and reuse the result. ### Convert a C string to a Mojo string Use `String(unsafe_from_utf8_ptr=...)` to copy a null-terminated C string into a Mojo string: ```mojo # Copies the bytes; uses `strlen()`. String(unsafe_from_utf8_ptr=c_string_ptr) ``` When you already know the length, you can wrap the C bytes in a non-copying, non-owning `Span[Byte]` and covert that to a Mojo `String`. For example, C's `strdup()` allocates and returns a copy of a string. You can wrap its result in a `Span`, convert the bytes to a Mojo string, then free the C-owned memory: ```mojo var name: String = "Echo" var cptr = external_call[ "strdup", Optional[Pointer[c_char, MutUntrackedOrigin]] ](name.as_c_string_span()) if cptr: var ptr = cptr.value() # Ask C for the length. A Mojo string's `byte_length()` measures the # Mojo side, which says nothing about the buffer C returned. var length = external_call["strlen", c_size_t](ptr) var span = Span(unsafe_ptr=ptr.unsafe_bitcast[Byte](), length=Int(length)) print(String(from_utf8=span)) # or from_utf8_lossy or unsafe_from_utf8 external_call["free", NoneType](ptr.unsafe_bitcast[NoneType]()) # free it ``` ### Convert Mojo string literals to C strings String literals can be passed to C APIs that expect a null-terminated `char*`. Call `as_c_string_span()` to access the C string: ```mojo "libm.so.6".as_c_string_span() ``` Mojo performs the conversion at compile time and embeds the null-terminated string in the compiled program. ## Memory management Mojo tracks the lifetime of its own memory. C memory has no Mojo value behind it, so there's nothing for Mojo to track. Every allocation that crosses the boundary still belongs to one side, and that side remains responsible for freeing it: - Free C memory with C's `free()`. - Let Mojo handle its own memory, except for unsafe allocations. ### Allocate C memory C allocators such as `malloc` return C-owned memory. `malloc` returns null when the allocation fails. Wrap the return type in `Optional`: ```mojo from std.ffi import external_call, c_size_t def create_buffer( n: c_size_t, ) -> Optional[Pointer[UInt8, MutUntrackedOrigin]]: return external_call[ "malloc", Optional[Pointer[UInt8, MutUntrackedOrigin]] ](n) def main() raises: var buf = create_buffer(c_size_t(16)) if not buf: raise Error("malloc failed") var ptr = buf.value() ptr[unsafe_offset=0] = 42 print(ptr[unsafe_offset=0]) # 42 external_call["free", NoneType](ptr.unsafe_bitcast[NoneType]()) ``` `MutUntrackedOrigin` tells Mojo not to reason about this pointer's lifetime. It's the opposite of every other origin on this page. Instead of tying the pointer to an owner, it says that no Mojo value owns *this* memory. You're responsible for keeping it valid and freeing it. You must free it with C's memory management functions, such as `free`. ### Free C memory automatically Pairing every `malloc()` with a matching `free()` by hand is easy to get wrong. A context manager can manage the allocation and release it for you. When the following block exits, `__exit__()` calls `free()`, even after a raised error: ```mojo from std.ffi import external_call, c_size_t struct CBuffer: var ptr: Pointer[UInt8, MutUntrackedOrigin] var size: c_size_t def __init__(out self, n: c_size_t) raises: self.size = n var allocated = external_call[ "malloc", Optional[Pointer[UInt8, MutUntrackedOrigin]] ](n) if not allocated: raise Error("malloc failed") self.ptr = allocated.value() def __enter__(self) -> Pointer[UInt8, MutUntrackedOrigin]: return self.ptr def __exit__(self): external_call["free", NoneType](self.ptr.unsafe_bitcast[NoneType]()) def main() raises: with CBuffer(c_size_t(1024)) as buf: buf[unsafe_offset=0] = 42 print(buf[unsafe_offset=0]) # 42 # The buffer is freed here. ``` ### Null returns C uses null pointers to mean "nothing" or "failed." A Mojo `Pointer` can't be null, so wrap any "maybe null" return in `Optional`. The `malloc` examples you just saw showed this pattern. `Optional`'s empty case adds nothing to the call and costs nothing to pass: ```mojo from std.ffi import external_call, c_char def main() raises: var name: String = "PATH" var found = external_call[ "getenv", Optional[Pointer[c_char, MutUntrackedOrigin]] ](name.as_c_string_span()) if found: print(String(unsafe_from_utf8_ptr=found.value())) else: print(t"{name} is not set") ``` Declaring an unwrapped, non-optional `Pointer` would compile. It would also treat C's null as a valid pointer. Dereferencing results in undefined behavior and will typically crash your program. ### Keeping Mojo values alive Pointers into Mojo memory carry an origin that tracks the value's lifetime. When you derive a pointer from a variable, Mojo keeps the variable alive while the pointer is live. It rejects code that would let the variable die first: ```mojo from std.ffi import OwnedDLHandle, c_size_t def main() raises: var proc = OwnedDLHandle() # No path: opens the current process. var c_strlen = proc.get_function[c_size_t]("strlen") # The pointer carries `line`'s origin, so `line` outlives the call. var line = String("Hello") var n = c_strlen(line.as_c_string_span()) print(t"length of '{line}': {n}") # 5 # Refill the same variable and call again. The origin still holds. line = "Hello, Mojo!" n = c_strlen(line.as_c_string_span()) print(t"length of '{line}': {n}") # 12 ``` The pointer's origin ties its lifetime to `line`. Mojo keeps `line` alive while C uses the pointer. As a result, you don't need workarounds to extend its lifetime. ## Safety Inside Mojo, the compiler checks types, tracks lifetimes through origins, and refuses code that would use a value after it dies. None of that reaches across the C boundary. C has no origins, no ownership, and no type information Mojo can read, so the compiler emits exactly the call you described and trusts you to have described it correctly. That makes you the type checker. The C header is the contract, and matching it is your job: - **Declare what C declares.** Use the `std.ffi` aliases so your types track the target's C ABI. A mismatch isn't a compile error, it's a wrong answer. - **Free memory on the side that allocated it.** C memory needs C's `free()`. Mojo memory has to outlive every C use, including uses that continue after the call returns. - **Assume undefined behavior, not exceptions.** A mismatched declaration usually produces a plausible result rather than a crash, so a passing test is weak evidence that a declaration is right. ## Unsafe operations Mojo marks operations it can't check for you with an `unsafe_` prefix, the same convention used throughout the standard library. This page uses four: `unsafe_ptr()` to hand C a raw pointer, `unsafe_bitcast()` to reinterpret one, `unsafe_offset=` to index past the first element, and `String(unsafe_from_utf8_ptr=)` to trust bytes C gave you. Each `unsafe_` operation marks a guarantee and responsibility you've taken over from the compiler. Origins still help wherever a pointer stays inside Mojo's view. Deriving a pointer from a variable, as in `line.as_c_string_span()`, keeps that variable alive for as long as the pointer lives. That protection ends when C stores the pointer somewhere Mojo can't see. `external_call()` and `OwnedDLHandle` are intentionally low level. Neither validates C signatures or protects you from ABI mismatches. Small declaration mistakes can produce plausible but incorrect results, while others fail only at build time or when you move to a different platform. Each entry names the API it applies to. ### Silently wrong at runtime - **Type matching** (both): Nothing validates your arguments or return type against the C declaration of the function you're calling. - **Return type width** (both): Declaring a narrower return than C returns keeps only the low bits. Declare `strchr`'s `char*` return as `c_int` and a pointer whose real value is 6199428535 comes back as 1904461239. That's truncation rather than noise, so a wrong value can still look plausible. Subtract two truncated pointers and the error cancels, giving you the right offset for the wrong reason. - **Argument type width and signedness** (both): Whatever you write becomes the declaration verbatim. Passing a `c_char` where C declares `int` has the callee reading a register the caller never fully set. - **Undeclared argument types** (`OwnedDLHandle`): `get_function()` takes the return type only, so nothing connects the arguments to the C function's real signature. Calling `get_function[c_double]("sqrt")` with a `c_int` returns `0.0` instead of failing. - **Raw `String` arguments** (`OwnedDLHandle`): `external_call()` rejects a `String` at compile time, but a callable from `get_function()` accepts one and reads whatever the struct's bytes happen to be. Passing a 53-byte `String` to `strlen` returns 5. Always pass `as_c_string_span()`. - **Pointers returned into a library** (`OwnedDLHandle`): When a C function returns a pointer into its own library, the return type must borrow from the handle, as in `Pointer[c_char, lib_origin]` where `comptime lib_origin = ImmOrigin(origin_of(lib))`. Declaring `ImmStaticOrigin` compiles, then reads freed memory once the handle closes the library. - **Variadic callees without `num_fixed_args`** (`external_call()`): Pass `num_fixed_args` for every C variadic function. Without it, each argument defaults to a fixed argument of a non-variadic callee, which gets the ABI wrong for `open()` or `snprintf()`. AAPCS on ARM64 macOS passes variadic arguments differently from fixed ones, so the mistake can work on x86-64 Linux and break on Apple silicon. - **Platform-varying C types** (both): `c_long` and `c_ulong` resolve per target rather than to one fixed width. Every platform Mojo supports today is LP64, so writing `Int64` for a C `long` happens to work. The alias says what you mean and keeps saying it if the supported targets change. - **Pointers C keeps after the call** (both): An origin protects a pointer for as long as Mojo can see it. Mojo can't see C storing your pointer for later, so a call returning doesn't mean C is finished with what you passed. Check the C documentation for whether a function retains the pointer. - **Non-nul-terminated buffers** (both): A bare `Pointer[c_char]` into a buffer with no terminator sends a C string function reading off the end. `CStringSpan` is the guardrail for this, ensuring a null terminator is present. ### Caught at build time These fail the build, but not always where you'd expect. - **Two signatures for one symbol in a module** (`external_call()`): Declaring `strchr` twice with different argument types in the same file fails to build. The diagnostic points into `std.ffi` rather than at either of your call sites. This is easy to hit when a wrapper and an inline call disagree. `get_function()` casts a runtime pointer instead, so it has no module-level declaration to collide. - **String arguments** (`external_call()`): Passing a `String` is rejected at compile time, and the error names `as_c_string_span()` as the fix. This is the one signature mistake the API catches for you. Take care because the checking is narrow, and `OwnedDLHandle` doesn't repeat it. - **Return types must be `RegisterPassable`** (both): `return_type` is bound to `RegisterPassable`, so the compiler rejects anything larger. A C function that returns a big struct by value isn't callable directly. C ABIs return those through a hidden pointer argument, so allocate the struct in Mojo, pass a pointer to it, and declare the return type as `NoneType`. ### Limitations - **`external_call()` can't load dynamic libraries**: It calls C functions by name and leaves the name for Mojo to resolve. Use `OwnedDLHandle` to load a dynamic library at runtime and retrieve its functions. - **Function resolution is by C symbol name** (both): C++ functions need `extern "C"` to be callable. - **`OwnedDLHandle` resolves everything at runtime**: A wrong library name or a missing symbol fails when the program runs, not when it builds, and the library has to be present on the machine that runs the program rather than the one that built it. `check_symbol()` tests whether a symbol exists and validates nothing about its signature. - **`mojo run` and `mojo build` resolve symbols differently** (`external_call()`): `mojo run` finds the symbol in the already loaded process image. `mojo build` links through a C compiler driver instead. libc arrives either way, so a running call is not proof that the same call will link. A symbol from another system library can resolve under `mojo run` and then fail under `mojo build` with `DSO missing from command line`. Name the library in `MODULAR_MOJO_MAX_SYSTEM_LIBS` when that happens. ### What the APIs do check Two guarantees `OwnedDLHandle` provides that `external_call()` doesn't: - A missing symbol raises an error rather than aborting the process, so you can probe for optional symbols. - The callable from `get_function()` borrows the handle, so the library can't be closed between the lookup and the call. ## C type reference {#c-type-reference} {/* markdownlint-disable MD013 */} | C type | `std.ffi` alias | Equivalent Mojo type | Notes | |------------------|-----------------|----------------------|-----------------------------------------------------| | `int` | `c_int` | `Int32` | the most common type by far | | `short` | `c_short` | `Int16` | | | `long` | `c_long` | depends on target | 64-bit on Linux and macOS | | `long long` | `c_long_long` | `Int64` | Always 64 bits. | | `unsigned char` | `c_uchar` | `UInt8` | | | `char` | `c_char` | `Int8` | signed; you'll mostly see it as `char*` | | `unsigned short` | `c_ushort` | `UInt16` | | | `unsigned int` | `c_uint` | `UInt32` | | | `unsigned long` | `c_ulong` | depends on target | matches `c_long` | | `float` | `c_float` | `Float32` | | | `double` | `c_double` | `Float64` | | | `size_t` | `c_size_t` | `UInt` | for sizes and counts | | `ssize_t` | `c_ssize_t` | `Int` | for sizes that can be negative | | `void*` | `OpaquePointer` | `Pointer[NoneType]` | see the [pointers section](#pointers), uses origins | {/* markdownlint-enable MD013 */} --- ## Control flow Mojo includes several traditional control flow structures for conditional and repeated execution of code blocks. ## The `if` statement Mojo supports the `if` statement for conditional code execution. With it you can conditionally execute an indented code block if a given [boolean](/docs/manual/types/#booleans) expression evaluates to `True`. ```mojo var temp_celsius = Float64(25) if temp_celsius > 20: print("It is warm.") print("The temperature is", temp_celsius * 9 / 5 + 32, "Fahrenheit." ) ``` ```output It is warm. The temperature is 77.0 Fahrenheit. ``` You can write the entire `if` statement as a single line if all you need to execute conditionally is a single, short statement. ```mojo var temp_celsius = 22 if temp_celsius < 15: print("It is cool.") # Skipped because condition is False if temp_celsius > 20: print("It is warm.") ``` ```output It is warm. ``` Optionally, an `if` statement can include any number of additional `elif` clauses, each specifying a boolean condition and associated code block to execute if `True`. The conditions are tested in the order given. When a condition evaluates to `True`, the associated code block is executed and no further conditions are tested. Additionally, an `if` statement can include an optional `else` clause providing a code block to execute if all conditions evaluate to `False`. ```mojo var temp_celsius = 25 if temp_celsius <= 0: print("It is freezing.") elif temp_celsius < 20: print("It is cool.") elif temp_celsius < 30: print("It is warm.") else: print("It is hot.") ``` ```output It is warm. ``` :::note Mojo doesn't support the equivalent of a Python `match` or C `switch` statement for pattern matching and conditional execution. ::: ### Short-circuit evaluation Mojo follows [short-circuit evaluation](https://en.wikipedia.org/wiki/Short-circuit_evaluation) semantics for boolean operators. If the first argument to an `or` operator evaluates to `True`, the second argument is not evaluated. ```mojo def true_func() -> Bool: print("Executing true_func") return True def false_func() -> Bool: print("Executing false_func") return False print('Short-circuit "or" evaluation') if true_func() or false_func(): print("True result") ``` ```output Short-circuit "or" evaluation Executing true_func True result ``` If the first argument to an `and` operator evaluates to `False`, the second argument is not evaluated. ```mojo print('Short-circuit "and" evaluation') if false_func() and true_func(): print("True result") ``` ```output Short-circuit "and" evaluation Executing false_func ``` ### Conditional expressions Mojo also supports conditional expressions (or what is sometimes called a [*ternary conditional operator*](https://en.wikipedia.org/wiki/Ternary_conditional_operator)) using the syntaxtrue_result if boolean_expression else false_result, just as in Python. This is most often used as a concise way to assign one of two different values to a variable, based on a boolean condition. ```mojo var temp_celsius = 15 var forecast = "warm" if temp_celsius > 20 else "cool" print("The forecast for today is", forecast) ``` ```output The forecast for today is cool ``` The alternative, written as a multi-line `if` statement, is more verbose. ```mojo var forecast: String if temp_celsius > 20: forecast = "warm" else: forecast = "cool" print("The forecast for today is", forecast) ``` ```output The forecast for today is cool ``` ## The `while` statement The `while` loop repeatedly executes a code block while a given boolean expression evaluates to `True`. For example, the following loop prints values from the Fibonacci series that are less than 50. ```mojo var fib_prev = 0 var fib_curr = 1 print(fib_prev, end="") while fib_curr < 50: print(",", fib_curr, end="") fib_prev, fib_curr = fib_curr, fib_prev + fib_curr ``` ```output 0, 1, 1, 2, 3, 5, 8, 13, 21, 34 ``` A `continue` statement skips execution of the rest of the code block and resumes with the loop test expression. ```mojo var n = 0 while n < 5: n += 1 if n == 3: continue print(n, end=", ") ``` ```output 1, 2, 4, 5, ``` A `break` statement terminates execution of the loop. ```mojo var n = 0 while n < 5: n += 1 if n == 3: break print(n, end=", ") ``` ```output 1, 2, ``` Optionally, a `while` loop can include an `else` clause. The body of the `else` clause executes when the loop's boolean condition evaluates to `False`, even if it occurs the first time tested. ```mojo var n = 5 while n < 4: print(n) n += 1 else: print("Loop completed") ``` ```output Loop completed ``` :::note The `else` clause does *not* execute if a `break` or `return` statement exits the `while` loop. ::: ```mojo var n = 0 while n < 5: n += 1 if n == 3: break print(n) else: print("Executing else clause") ``` ```output 1 2 ``` ## The `for` statement The `for` loop iterates over a sequence, executing a code block for each element in the sequence. The Mojo `for` loop can iterate over any type that implements an `__iter__()` method that returns a type that defines `__next__()` and `__len__()` methods. ### Iterating over Mojo collections All of the collection types in the [`collections`](/docs/std/collections/) module support `for` loop iteration. See the [Collection types](/docs/manual/types/#collection-types) documentation for more information on Mojo collection types. The following shows an example of iterating over a Mojo [`List`](/docs/std/collections/list/List/). ```mojo var states: List[String] = ["California", "Hawaii", "Oregon"] for state in states: print(state) ``` ```output California Hawaii Oregon ``` The same technique works for iterating over a Mojo [`Set`](/docs/std/collections/set/Set/). ```mojo from std.collections import Set var values = {42, 0} for item in values: print(item) ``` ```output 42 0 ``` There are two techniques for iterating over a Mojo [`Dict`](/docs/std/collections/dict/Dict/). The first is to iterate directly using the `Dict`, which produces a sequence of the dictionary's keys. ```mojo var capitals: Dict[String, String] = { "California": "Sacramento", "Hawaii": "Honolulu", "Oregon": "Salem" } for var state in capitals: print(t"{capitals[state]}, {state}") ``` ```output Sacramento, California Honolulu, Hawaii Salem, Oregon ``` The second approach to iterating over a Mojo `Dict` is to invoke its [`items()`](/docs/std/collections/dict/Dict/#items) method, which produces a sequence of [`DictEntry`](/docs/std/collections/dict/#dictentry) objects. Within the loop body, you can then access the `key` and `value` fields of the entry. ```mojo for item in capitals.items(): print(t"{item.value}, {item.key}") ``` ```output Sacramento, California Honolulu, Hawaii Salem, Oregon ``` #### Iterating using references The Mojo collection iterators all return [references](/docs/manual/values/lifetimes/#working-with-references), which are captured immutably into the loop variable. If you'd like to get a reference to a mutable element, add the `ref` keyword in front of the loop variable to create a reference binding that matches the element reference. This can be useful if you want to mutate the value in the collection: ```mojo var values: List[Int] = [1, 4, 7, 3, 6, 11] for ref value in values: if value % 2 != 0: value -= 1 print(values) ``` ```output [0, 4, 6, 2, 6, 10] ``` ### Iterating ranges Another type of iterable provided by the Mojo standard library is a *range*, which is a sequence of integers generated by the [`range()`](/docs/std/builtin/range/range/) function. It differs from the collection types shown above in that it's implemented as a [generator](https://en.wikipedia.org/wiki/Generator_\(computer_programming\)), producing each value as needed rather than materializing the entire sequence in memory. For example: ```mojo for i in range(5): print(i, end=", ") ``` ```output 0, 1, 2, 3, 4, ``` ### `for` loop control statements A `continue` statement skips execution of the rest of the code block and resumes the loop with the next element of the collection. ```mojo for i in range(5): if i == 3: continue print(i, end=", ") ``` ```output 0, 1, 2, 4, ``` A `break` statement terminates execution of the loop. ```mojo for i in range(5): if i == 3: break print(i, end=", ") ``` ```output 0, 1, 2, ``` Optionally, a `for` loop can include an `else` clause. The body of the `else` clause executes after iterating over all of the elements in a collection. ```mojo for i in range(5): print(i, end=", ") else: print("\nFinished executing 'for' loop") ``` ```output 0, 1, 2, 3, 4, Finished executing 'for' loop ``` The `else` clause executes even if the collection is empty. ```mojo from std.collections import List var empty: List[Int] = [] for i in empty: print(i) else: print("Finished executing 'for' loop") ``` ```output Finished executing 'for' loop ``` :::note The `else` clause does *not* execute if a `break` or `return` statement terminates the `for` loop. ::: ```mojo from std.collections import List var animals: List[String] = ["cat", "aardvark", "hippopotamus", "dog"] for animal in animals: if animal == "dog": print("Found a dog") break else: print("No dog found") ``` ```output Found a dog ``` ### Iterating over Python collections The Mojo `for` loop supports iterating over Python collection types. Each item retrieved by the loop is a [`PythonObject`](/docs/std/python/python_object/PythonObject/) wrapper around the Python object. Refer to the [Python types](/docs/manual/python/types/) documentation for more information on manipulating Python objects from Mojo. The following is a simple example of iterating over a mixed-type Python list. ```mojo from std.python import Python def main() raises: # Create a mixed-type Python list var py_list = Python.list(42, "cat", 3.14159) for py_obj in py_list: # Each element is of type "PythonObject" print(py_obj) ``` ```output 42 cat 3.14159 ``` There are two techniques for iterating over a Python dictionary. The first is to iterate directly using the dictionary, which produces a sequence of its keys. ```mojo from std.python import Python def main() raises: # Create a mixed-type Python dictionary var py_dict = Python.evaluate("{'a': 1, 'b': 2.71828, 'c': 'sushi'}") for py_key in py_dict: # Each key is of type "PythonObject" print(py_key, py_dict[py_key]) ``` ```output a 1 b 2.71828 c sushi ``` The second approach to iterating over a Python dictionary is to invoke its `items()` method, which produces a sequence of 2-tuple objects. Within the loop body, you can then access the key and value by index. ```mojo from std.python import Python def main() raises: # Create a mixed-type Python dictionary var py_dict = Python.evaluate("{'a': 1, 'b': 2.71828, 'c': 'sushi'}") for py_tuple in py_dict.items(): # Each 2-tuple is of type "PythonObject" print(py_tuple[0], py_tuple[1]) ``` ```output a 1 b 2.71828 c sushi ``` --- ## Errors, error handling, and context managers Mojo represents errors as values—specifically, as alternate return values from functions. Unlike stack-unwinding exceptions in languages like C++ or Java, Mojo errors don't require expensive call stack unwinding, so their runtime overhead is as low as returning and checking an extra `Bool`. This design also enables error handling in contexts where traditional exceptions aren't available, like GPU kernels. This page covers: - [**Raise an error**](#raise-an-error) — Use the built-in `Error` type to raise errors with string messages. - [**Handle an error**](#handle-an-error) — Use `try`/`except`/`else`/`finally` to detect and recover from errors. - [**Typed errors**](#typed-errors) — Define custom error types as structs for structured error data and compile-time type checking. - [**Representing multiple error conditions**](#representing-multiple-error-conditions) — Use enumerated error types or the `Variant` type for pattern matching. - [**The `Never` type**](#the-never-type) — Mark functions that always raise or never raise. - [**Parametric raises**](#parametric-raises) — Write parameterized functions that propagate error types from their arguments. - [**Typed and `Error` interaction**](#typed-errors-and-error-interaction) — Work with code that uses both error styles. - [**Stack traces**](#enable-stack-trace-generation-for-errors) — Enable stack trace collection for debugging. - [**Context managers**](#use-a-context-manager) — Manage resources safely with the `with` statement. An error interrupts the normal execution flow of your program. If you provide an error handler (using [`try`/`except`](#handle-an-error)) in the current function, execution resumes with that handler. If the error isn't handled in the current function, it propagates to the calling function, and so on. If an error isn't caught by any handler, your program terminates with a non-zero exit code and prints the error message: ```output Unhandled exception caught during execution: record not found ``` ## Raise an error The built-in [`Error`](/docs/std/builtin/error/Error/) type is the default error type for most Mojo code. It carries a text message describing what went wrong, and it's the right choice for application-level error handling—simple, well-supported, and sufficient for the majority of use cases. You can raise an `Error` with the initializer or a string literal shorthand: ```mojo # These are equivalent raise Error("file not found") raise "file not found" ``` The string literal form is a convenience—the compiler automatically wraps it in an `Error`. By declaring `raises`, you tell Mojo that a function may raise an error: ```mojo def read_file_fn(path: String) raises -> String: if not path: raise "path cannot be empty" return "contents of " + path ``` :::tip If you need structured error data—like separate fields for an error code and description—or allocation-free errors for GPU kernels, see [Typed errors](#typed-errors) later on this page. For most application code, `Error` with a descriptive string message is all you need. ::: ## Handle an error Mojo uses `try`/`except` to detect and handle errors. The full syntax is: ```mojo try: # Code that might raise an error except e: # Runs if an error occurs else: # Runs if no error occurs finally: # Always runs, regardless of outcome ``` You must include one or both of `except` and `finally`. The `else` clause is optional. ### How each clause works - `try` — Contains code that might raise an error. If no error occurs, the entire block executes. If an error occurs, execution stops at the `raise` point and continues with the `except` clause (if present) or the `finally` clause. - `except` — Runs only when an error occurs in the `try` block. If you provide a variable name (`except e:`), the error is bound to that variable. A `try` block can have only one `except` clause. - `else` — Runs only when no error occurs in the `try` block. The `else` clause is *skipped* if the `try` clause exits via `continue`, `break`, or `return`. - `finally` — Runs after the `try` and any `except` or `else` clause, regardless of outcome. It executes even if another clause exits via `continue`, `break`, `return`, or by raising a new error. Use `finally` to release resources (such as file handles) that must be cleaned up regardless of whether an error occurred. ### Example The following example demonstrates all four clauses. The `process_record()` function raises `Error` for different conditions, and the caller loops over a list of IDs to exercise each clause: ```mojo title="handle_error.mojo" def process_record(id: Int) raises -> String: if id < 0: raise Error("invalid record ID: must be non-negative") if id > 999: raise Error("record not found") return String("record_", id) def main() raises: try: for id in [5, 0, 1001, -3, 42]: var result: String try: print() print("try => id:", id) if id == 0: continue result = process_record(id) except e: if "invalid" in String(e): print("except => fatal:", e) raise e print("except => handled:", e) else: print("else => success:", result) finally: print("finally => done with id:", id) except e: print("\nre-raised error:", e) ``` ```output try => id: 5 else => success: record_5 finally => done with id: 5 try => id: 0 finally => done with id: 0 try => id: 1001 except => handled: record not found finally => done with id: 1001 try => id: -3 except => fatal: invalid record ID: must be non-negative finally => done with id: -3 re-raised error: invalid record ID: must be non-negative ``` Notice: - When `id` is 5: `process_record()` succeeds, so `else` runs, then `finally`. - When `id` is 0: `continue` exits the `try` block, skipping both `except` and `else`. Only `finally` runs. - When `id` is 1001: `process_record()` raises an error. The `except` clause handles it and execution continues with the next iteration. - When `id` is -3: `process_record()` raises an "invalid" error. The `except` clause re-raises it, so it propagates to the outer `try`/`except`. The `finally` clause still runs before the error propagates. Because the re-raise exits the loop, `id` 42 is never processed. ### Re-raise an error To re-raise a caught error, pass it to `raise`: ```mojo try: var result = process_record(-1) except e: print("Logging error:", e) raise e # re-raise ``` You can also raise a different error from within an `except` clause. Re-raising copies the error, which is cheap because both its message and its optional stack trace are reference counted. To avoid the copy, transfer the error with the [transfer sigil](/docs/manual/values/ownership/#transfer-arguments-var-and-): `raise e^`. A [custom error type](#typed-errors) that doesn't conform to [`ImplicitlyCopyable`](/docs/std/traits/copyable/ImplicitlyCopyable/) requires the transfer sigil to re-raise. ## Typed errors For code that needs more than a string message—like standard library APIs, GPU abstractions, or situations where callers need structured error data—Mojo lets you define custom error types as [structs](/docs/manual/structs/). ### Define a custom error type In Mojo, any struct can serve as an error type—no special base class or trait is required. However, implementing the [`Writable`](/docs/std/format/Writable/) trait is recommended so the error produces a readable message when printed or when the program terminates with an unhandled error: ```mojo @fieldwise_init struct ValidationError(Copyable, Writable): var field: String var reason: String def write_to(self, mut writer: Some[Writer]): writer.write("ValidationError(", self.field, "): ", self.reason) ``` The [`@fieldwise_init`](/docs/reference/decorators/fieldwise-init/) decorator generates an `__init__()` method with an argument for each field, so you can construct errors like `ValidationError("username", "too short")` or with keyword arguments like `ValidationError(field="username", reason="too short")`. :::note Typed errors work on GPUs and embedded targets as long as you avoid heap-allocated types like `String`. For more on GPU programming, see [GPU fundamentals](https://max.modular.com/gpu/fundamentals/). ::: ### Raise a typed error To declare that a function can raise a typed error, add `raises YourErrorType` to its signature: ```mojo def validate_username(username: String) raises ValidationError -> String: if username.byte_length() == 0: raise ValidationError(field="username", reason="cannot be empty") if username.count_codepoints() < 3: raise ValidationError( field="username", reason="must be at least 3 characters" ) return username ``` Mojo functions are non-raising by default. Including `raises` (with or without a type) makes it a raising function. Each function can declare at most one error type. The compiler enforces this—if any `raise` statement in the function body doesn't match the declared type, the program won't compile. If a non-raising function calls a raising function, it must handle the error locally: ```mojo # This doesn't compile — validate_username() can raise def process_name(name: String): print(validate_username(name)) # This compiles — the error is handled def process_name_safe(name: String): try: print(validate_username(name)) except e: print("Invalid:", e) ``` ### Catch a typed error Use `try`/`except` to catch a typed error. The compiler automatically infers the error type from the function being called, so `except e:` gives you a fully typed error value—no casting required: ```mojo try: var name = validate_username("") except e: # e is a ValidationError — access fields directly print("Error in field '" + e.field + "': " + e.reason) ``` Which produces this output: ```output Error in field 'username': cannot be empty ``` A `try` block can include only one `except` clause. Mojo doesn't support `except ErrorType as e:` syntax—the type is always inferred from the function being called. If you need to handle calls that raise different error types, use separate `try` blocks: ```mojo # Each try block handles one error type try: var name = validate_username(input) except e: # e is a ValidationError — access fields directly print("Validation failed:", e.field, e.reason) try: var file = open_file(path) except e: # e is a FileError — match on variant if e == FileError.not_found: print("Missing:", path) ``` :::note If your error type implements the [`Writable`](/docs/std/format/Writable/) trait, you can also pass `e` directly to `print()`: ```mojo except e: print(e) # calls ValidationError.write_to() ``` Which produces this output: ```output ValidationError(username): cannot be empty ``` ::: ## Representing multiple error conditions Each function can declare only one error type in its `raises` clause. When a function can fail in multiple distinct ways, you need to represent those conditions within a single type. Mojo offers two approaches: - **Enumerated error types** — A single struct with `comptime` variant aliases. Simpler and more efficient when you only need to distinguish between conditions. - **The `Variant` type** — The standard library [`Variant`](/docs/std/utils/variant/Variant/) type with separate structs per condition. More flexible when each condition needs to carry different data. ### Enumerated error types A single struct can represent all error conditions using an integer `_variant` field and [`comptime` values](/docs/manual/metaprogramming/comptime-evaluation/#comptime-values) as named constants. The `write_to` method generates human-readable strings from the variant code: ```mojo @fieldwise_init struct FileError(Equatable, ImplicitlyCopyable, Writable): var _variant: Int # Compile-time constant variants comptime not_found = FileError(_variant=1) comptime permission_denied = FileError(_variant=2) comptime already_exists = FileError(_variant=3) def variant_name(self) -> String: if self._variant == 1: return "not_found" elif self._variant == 2: return "permission_denied" elif self._variant == 3: return "already_exists" return "unknown" def write_to(self, mut writer: Some[Writer]): writer.write("FileError.", self.variant_name()) ``` Because `FileError` has a single `Int` field and conforms to [`Equatable`](/docs/std/builtin/comparable/Equatable/), the compiler auto-synthesizes `__eq__()`, so you can compare variants directly: ```mojo def open_file(path: String) raises FileError -> String: if not path: raise FileError.not_found if path == "/secret": raise FileError.permission_denied return "Contents of " + path ``` You can then match on specific variants in the handler: ```mojo try: print(open_file("/secret")) except e: if e == FileError.not_found: print("Not found:", e) elif e == FileError.permission_denied: print("Permission denied:", e) ``` Which produces this output: ```output Permission denied: FileError.permission_denied ``` ### The `Variant` type When each error condition needs to carry different data, you can use the standard library [`Variant`](/docs/std/utils/variant/Variant/) type instead of an integer-based enumeration. Define a separate struct for each condition, then combine them into a single error type with a `comptime` alias: ```mojo title="variant_errors.mojo" from std.utils import Variant @fieldwise_init struct NotFoundError(Copyable, Writable): var path: String def write_to(self, mut writer: Some[Writer]): writer.write("file not found: ", self.path) @fieldwise_init struct PermissionError(Copyable, Writable): var path: String var required_role: String def write_to(self, mut writer: Some[Writer]): writer.write( "permission denied on ", self.path, " (requires ", self.required_role, ")", ) comptime FileError = Variant[NotFoundError, PermissionError] ``` Construct a `Variant` by wrapping the inner error in the `Variant` type: ```mojo def open_file(path: String) raises FileError -> String: if not path: raise FileError(NotFoundError("")) if path == "/secret": raise FileError(PermissionError("/secret", "admin")) return "Contents of " + path ``` In the handler, use `.isa[T]()` to test which condition occurred and `e[T]` to access the inner error with its full type: ```mojo try: print(open_file("/secret")) except e: if e.isa[NotFoundError](): print("Not found:", e[NotFoundError]) elif e.isa[PermissionError](): print("Access denied:", e[PermissionError]) ``` ```output Access denied: permission denied on /secret (requires admin) ``` Use the `Variant` approach when each condition carries different fields (like `path` vs `path` + `required_role` above). Use the [enumerated error type](#enumerated-error-types) pattern when you only need to distinguish between conditions without carrying different data per condition. ## The `Never` type `Never` is a type with no initializers. It can't be instantiated. This makes it useful in error-handling signatures to express two opposite guarantees: - `raises YourErrorType -> Never` — The function *always* raises and never returns a value. This is useful for functions like `panic()` that unconditionally signal an error. - `raises Never -> ReturnType` — The function *never* raises and always returns a value. This is equivalent to omitting `raises` entirely. ### Functions that always raise A function with `-> Never` as its return type must never terminate with a `return` statement—it must raise on every code path (or loop infinitely). Because `Never` can substitute for any type, the compiler allows using such a function in place of a return value: ```mojo # Always raises, never returns def panic(msg: String) raises -> Never: raise Error(msg) def get_value_or_panic(maybe: Optional[Int]) raises -> Int: if maybe: return maybe.value() # Never substitutes for Int in this branch panic("value is missing") ``` ### Functions that never raise A function with `raises Never` guarantees at compile time that it never raises. This is equivalent to writing a plain non-raising function: ```mojo # These two signatures are equivalent: def safe_add(a: Int, b: Int) raises Never -> Int: return a + b def safe_add(a: Int, b: Int) -> Int: return a + b ``` This equivalency is especially useful in combination with [parametric raises](#parametric-raises), where the compiler infers `raises Never` when a function argument doesn't raise. ## Parametric raises You can write [parameterized functions](/docs/manual/parameters/#parameters-and-generics) that propagate the error type from a function argument to the caller. This uses a compile-time parameter for the error type: ```mojo def run_action[ ErrorType: AnyType ](action: def() thin raises ErrorType -> Int) raises ErrorType -> Int: return action() ``` The function type uses `thin` because `action` is a noncapturing function value. The `ErrorType` parameter is inferred from the function you pass in. If the function raises `NetworkError`, then `run_action` raises `NetworkError`. If the function raises `ParseError`, then `run_action` raises `ParseError`: ```mojo def fetch_data() raises NetworkError -> Int: raise NetworkError(code=404) def parse_config() raises ParseError -> Int: raise ParseError(position=42) # ... # ErrorType inferred as NetworkError try: _ = run_action(fetch_data) except e: print("Network failure:", e) # ErrorType inferred as ParseError try: _ = run_action(parse_config) except e: print("Parse failure:", e) ``` If the function argument doesn't raise at all, the compiler infers `Never` as the error type. This means `run_action` itself becomes non-raising, and no `try` block is needed: ```mojo def get_value() -> Int: return 99 # ... # ErrorType inferred as Never — no try block needed var result = run_action(get_value) print("Got value:", result) ``` Which produces this output: ```output Got value: 99 ``` ## Typed errors and `Error` interaction Most codebases contain a mix of functions that raise, don't raise, or raise typed errors. This section covers how the styles interact and how to work with them effectively. ### Wrap `Error` at API boundaries When calling raising functions or other `Error`-raising code from a function that uses typed errors, catch the `Error` and convert it: ```mojo def validate_with_error(value: Int) raises -> Int: if value < 0: raise "value cannot be negative" return value def wrapped_validate(value: Int) raises ValidationError -> Int: try: return validate_with_error(value) except e: raise ValidationError(field="value", reason=String(e)) ``` ### Avoid bare `raises` with typed errors Using bare `raises` (without a type) on an function that calls typed-error functions causes *type erasure*—the compiler forgets the specific error type, even though the runtime preserves the error's identity: ```mojo # Anti-pattern: bare raises erases type info at compile time def validate_bare_raises(value: Int) raises -> Int: return validate_typed(value) ``` The caller of `validate_bare_raises()` receives an `Error`, not a `ValidationError`: ```mojo try: _ = validate_bare_raises(-5) except e: # e is typed as Error — no field access available # e.field would not compile here print(e) ``` ```output ValidationError(value): cannot be negative ``` The error message still shows `ValidationError` because the runtime preserves the original error's [`Writable`](/docs/std/format/Writable/) output. But the compiler sees only `Error`, so you lose access to structured fields. Always use `raises YourErrorType` to maintain type safety. Note that type erasure only affects *uncaught* errors that propagate through a bare `raises` function. If you catch the typed error locally, you still get full field access: ```mojo def error_caller(): try: _ = validate_typed(-5) except e: # e is a ValidationError — field access works print("Field:", e.field, "Reason:", e.reason) ``` ```output Field: value Reason: cannot be negative ``` ### Don't mix error types in a single `try` block You can't call functions that raise different error types in the same `try` block. The compiler rejects the mismatch: ```mojo def error_func() raises -> Int: raise "something went wrong" def typed_func() raises ValidationError -> Int: raise ValidationError(field="x", reason="invalid") # This doesn't compile def mixed() raises ValidationError: try: _ = error_func() # raises Error _ = typed_func() # raises ValidationError except e: print(e) ``` The compiler reports: ```output error: cannot call function that may raise 'Error' in a context that supports an error type of 'ValidationError' ``` To call both functions, use separate `try` blocks or wrap the `Error`-raising function as shown in [Wrap `Error` at API boundaries](#wrap-error-at-api-boundaries). ### Recommendations for mixed codebases When working with both `Error` and typed errors: - **Use `raises YourErrorType`** — Always specify the error type in function signatures. Bare `raises` discards type information. - **Use separate `try` blocks** — When calling functions with different error types, use nested or sequential `try` blocks to handle each type independently. :::note For a complete working example of these interaction patterns, see [`error_interaction.mojo`](https://github.com/modular/modular/blob/mojo/v1.1.0/Mojo/docs/site/code/manual/errors/error_interaction.mojo). ::: ## Enable stack trace generation for errors Because Mojo represents errors as alternate return values rather than stack-unwinding exceptions, stack trace collection isn't automatic. Collecting a stack trace requires heap allocation and adds runtime overhead, so it's disabled by default to keep error handling lightweight. :::important Stack traces are a feature of the built-in `Error` type only. Typed errors currently don't capture stack traces because the trace is collected inside `Error.__init__()`, and custom error structs have no equivalent hook. The examples in this section all use `Error` intentionally. ::: Mojo generates a stack trace when your program hits a segmentation fault. However, by default Mojo *doesn't* generate a stack trace when your program raises an error—this avoids the additional runtime overhead. To enable stack traces for raised errors, set the `MODULAR_DEBUG` environment variable to `stack-trace-on-error`, as shown in the examples below. Keep in mind that when you compile your program with [`mojo build`](/docs/cli/build/), the compiler optimizes and strips symbols by default, so often your stack trace won't be very useful. Consider this program: ```mojo title="stacktrace_error.mojo" def func2() raises -> None: raise Error("Intentional error") def func1() raises -> None: func2() def main() raises: func1() ``` If you compile the program with default settings and run it with the environment variable set, you'll see a stack trace without symbols: ```sh mojo build stacktrace_error.mojo ``` ```sh MODULAR_DEBUG=stack-trace-on-error ./stacktrace_error ``` ```output #0 0x... llvm::sys::PrintStackTrace(llvm::raw_ostream&, int) #1 0x... KGEN_CompilerRT_GetStackTrace #2 0x... main (./stacktrace_error+...) Unhandled exception caught during execution: Intentional error ``` To generate a more useful stack trace, compile the program with `--debug-level full` (or `-g`) to include debug symbols: ```sh mojo build --debug-level full stacktrace_error.mojo ``` ```sh MODULAR_DEBUG=stack-trace-on-error ./stacktrace_error ``` ```output #0 0x... llvm::sys::PrintStackTrace(llvm::raw_ostream&, int) #1 0x... KGEN_CompilerRT_GetStackTrace #2 0x... Error.__init__[...](...) .../builtin/error.mojo:159:38 #3 0x... stacktrace_error::func2() stacktrace_error.mojo:14:16 #4 0x... stacktrace_error::func1() stacktrace_error.mojo:18:10 #5 0x... stacktrace_error::main() stacktrace_error.mojo:22:10 #6 0x... __wrap_and_execute_raising_main[...](...) .../builtin/_startup.mojo:88:18 #7 0x... main .../builtin/_startup.mojo:103:4 Unhandled exception caught during execution: Intentional error ``` With debug symbols, the trace shows the function call chain and source locations: `main()` → `func1()` → `func2()` → `Error.__init__()`. :::note Running your program directly with [`mojo run`](/docs/cli/run/) or `mojo` doesn't include debug symbols in the stack trace, even with `--debug-level full`. Use `mojo build` with `-g` and run the compiled binary for symbolicated stack traces. ::: ### Capture a stack trace programmatically You can bind the `Error` instance to a variable in the `except` clause and call its [`get_stack_trace()`](/docs/std/builtin/error/Error/#get_stack_trace) method to get the stack trace as an `Optional[String]`. The method returns `None` if stack trace collection was disabled or unavailable: ```mojo title="stacktrace_error_capture.mojo" def func2() raises -> None: raise Error("Intentional error") def func1() raises -> None: func2() def main() raises: try: func1() except e: print(e) print("-" * 20) var stack_trace = e.get_stack_trace() if stack_trace: print(stack_trace.value()) else: print("No stack trace available") ``` When you compile with debug symbols and run with stack trace generation enabled: ```sh mojo build --debug-level full stacktrace_error_capture.mojo ``` ```sh MODULAR_DEBUG=stack-trace-on-error ./stacktrace_error_capture ``` ```output Intentional error -------------------- #0 0x... llvm::sys::PrintStackTrace(llvm::raw_ostream&, int) #1 0x... KGEN_CompilerRT_GetStackTrace #2 0x... Error.__init__[...](...) .../builtin/error.mojo:159:38 #3 0x... stacktrace_error_capture::func2() stacktrace_error_capture.mojo:14:16 #4 0x... stacktrace_error_capture::func1() stacktrace_error_capture.mojo:18:10 #5 0x... stacktrace_error_capture::main() stacktrace_error_capture.mojo:23:14 #6 0x... __wrap_and_execute_raising_main[...](...) .../builtin/_startup.mojo:88:18 #7 0x... main .../builtin/_startup.mojo:103:4 ``` Without enabling stack trace generation, the output is: ```output Intentional error -------------------- No stack trace available ``` ## Use a context manager A *context manager* is an object that manages resources such as files, network connections, and database connections. It provides a way to allocate resources and release them automatically when they are no longer needed, ensuring proper cleanup and preventing resource leaks even when errors occur. :::note Context managers work with both typed errors and the built-in `Error` type. The `with` statement handles either error style transparently. ::: As an example, consider reading data from a file. A naive approach might look like this: ```mojo # Obtain a file handle to read from storage var f = open(input_file, "r") var content = f.read() # Process the content as needed # Close the file handle f.close() ``` Calling [`close()`](/docs/std/io/file/FileHandle/#close) releases the memory and other operating system resources associated with the opened file. If your program were to open many files without closing them, you could exhaust the resources available to your program and cause errors. The problem is even worse if you were writing to a file instead of reading from it, because the operating system might buffer the output in memory until the file is closed. If your program were to crash instead of exiting normally, that buffered data could be lost instead of being written to storage. The example above includes the call to `close()`, but it ignores the possibility that [`read()`](/docs/std/io/file/FileHandle/#read) could raise an error, which would prevent the `close()` from executing. To handle this scenario, you could rewrite the code to use `try` like this: ```mojo # Obtain a file handle to read from storage var f = open(input_file, "r") try: var content = f.read() # Process the content as needed finally: # Ensure that the file handle is closed even if read() raises an error f.close() ``` However, the [`FileHandle`](/docs/std/io/file/FileHandle/) struct returned by [`open()`](/docs/std/io/file/open/) is a context manager. When used with Mojo's `with` statement, a context manager ensures that the resources it manages are properly released at the end of the block, even if an error occurs. In the case of a `FileHandle`, that means the call to `close()` takes place automatically. So you could rewrite the example above to take advantage of the context manager (and omit the explicit call to `close()`) like this: ```mojo with open(input_file, "r") as f: var content = f.read() # Process the content as needed ``` The `with` statement also allows you to use multiple context managers within the same code block. As an example, the following code opens one text file, reads its entire content, converts it to upper case, and then writes the result to a different file: ```mojo with open(input_file, "r") as f_in, open(output_file, "w") as f_out: var input_text = f_in.read() var output_text = input_text.upper() f_out.write(output_text) ``` `FileHandle` is perhaps the most commonly used context manager. Other examples of context managers in the Mojo standard library are [`NamedTemporaryFile`](/docs/std/tempfile/tempfile/NamedTemporaryFile/), [`TemporaryDirectory`](/docs/std/tempfile/tempfile/TemporaryDirectory/), [`BlockingScopedLock`](/docs/std/utils/lock/BlockingScopedLock/), and [`assert_raises`](/docs/std/testing/testing/assert_raises/). You can also create your own custom context managers, as described in [Write a custom context manager](#write-a-custom-context-manager) below. ## Write a custom context manager Writing a custom context manager is a matter of defining a [struct](/docs/manual/structs/) that implements two special *dunder* methods ("double underscore" methods): `__enter__()` and `__exit__()`: - `__enter__()` is called by the `with` statement to enter the runtime context. The `__enter__()` method should initialize any state necessary for the context and return the context manager. - `__exit__()` is called when the `with` code block completes execution, even if the `with` code block terminates with a call to `continue`, `break`, or `return`. The `__exit__()` method should release any resources associated with the context. After the `__exit__()` method returns, the context manager is destroyed. If the `with` code block raises an error, then the `__exit__()` method runs before any error processing occurs (that is, before it is caught by a `try`/`except` structure or your program terminates). If you'd like to define conditional processing for error conditions in a `with` code block, you can implement an overloaded version of `__exit__()` that takes an error argument. For more information, see [Define a conditional `__exit__()` method](#define-a-conditional-__exit__-method) and [Handle typed errors in `__exit__()`](#handle-typed-errors-in-__exit__) below. For context managers that don't need to release resources or perform other actions on termination, you are not required to implement an `__exit__()` method. In that case the context manager is destroyed automatically after the `with` code block completes execution. Here is an example of implementing a `Timer` context manager, which prints the amount of time spent executing the `with` code block: ```mojo title="context_mgr.mojo" import std.sys import std.time @fieldwise_init struct Timer(ImplicitlyCopyable): var start_time: Int def __init__(out self): self.start_time = 0 def __enter__(mut self) -> Self: self.start_time = Int(time.perf_counter_ns()) return self def __exit__(mut self): var end_time = time.perf_counter_ns() var elapsed_time_ms = round( Float64(end_time - self.start_time) / 1e6, 3 ) print("Elapsed time:", elapsed_time_ms, "milliseconds") def main() raises: with Timer(): print("Beginning execution") time.sleep(1.0) if len(sys.argv()) > 1: raise "simulated error" time.sleep(1.0) print("Ending execution") ``` Running this example produces output like this: ```sh mojo context_mgr.mojo ``` ```output Beginning execution Ending execution Elapsed time: 2010.0 milliseconds ``` ```sh mojo context_mgr.mojo fail ``` ```output Beginning execution Elapsed time: 1002.0 milliseconds Unhandled exception caught during execution: simulated error ``` ### Define a conditional `__exit__()` method When creating a context manager, you can implement the `__exit__(self)` form of the `__exit__()` method to handle completion of the `with` statement under all circumstances including errors. However, you have the option of additionally implementing an overloaded version that is invoked instead when an `Error` occurs in the `with` code block: ```mojo def __exit__(self, error: Error) raises -> Bool ``` Given the `Error` that occurred as an argument, the method can do any of the following: - Return `True` to suppress the error. - Return `False` to re-raise the error. - Raise a new error. The following is an example of a context manager that suppresses only a certain error condition and propagates all others: ```mojo title="conditional_context_mgr.mojo" import std.time @fieldwise_init struct ConditionalTimer(ImplicitlyCopyable): var start_time: Int def __init__(out self): self.start_time = 0 def __enter__(mut self) -> Self: self.start_time = Int(time.perf_counter_ns()) return self def __exit__(mut self): var end_time = time.perf_counter_ns() var elapsed_time_ms = round( Float64(end_time - self.start_time) / 1e6, 3 ) print("Elapsed time:", elapsed_time_ms, "milliseconds") def __exit__(mut self, e: Error) -> Bool: if String(e) == "just a warning": print("Suppressing error:", e) self.__exit__() return True else: print("Propagating error") self.__exit__() return False def flaky_identity(n: Int) raises -> Int: if (n % 4) == 0: raise "really bad" elif (n % 2) == 0: raise "just a warning" else: return n def main() raises: for i in range(1, 9): with ConditionalTimer(): print("\nBeginning execution") print("i =", i) time.sleep(0.1) if i == 3: print("continue executed") continue var j = flaky_identity(i) print("j =", j) print("Ending execution") ``` Running this example produces this output: ```output Beginning execution i = 1 j = 1 Ending execution Elapsed time: 105.0 milliseconds Beginning execution i = 2 Suppressing error: just a warning Elapsed time: 106.0 milliseconds Beginning execution i = 3 continue executed Elapsed time: 106.0 milliseconds Beginning execution i = 4 Propagating error Elapsed time: 106.0 milliseconds Unhandled exception caught during execution: really bad ``` ### Handle typed errors in `__exit__()` The `__exit__(self, error: Error)` overload handles only `Error` values. To handle typed errors, implement a parameterized `__exit__()` method with a compile-time error type parameter: ```mojo def __exit__[ErrType: AnyType](self, err: ErrType) -> Bool ``` This method receives the typed error directly, preserving its full type information. You can use [reflection](/docs/manual/metaprogramming/reflection/) to inspect the error type at compile time. For example, you can: - `reflect[ErrType].name()` — Get the error type's name as a string. - `comptime if conforms_to(ErrType, Writable)` — Check if the error implements `Writable`, and if so, access the error through its `Writable` interface. The following `ResourceGuard` example demonstrates this pattern: ```mojo from std.reflection import * @fieldwise_init struct ConnectionError(Copyable, Writable): var message: String def write_to(self, mut writer: Some[Writer]): writer.write("ConnectionError: ", self.message) struct ResourceGuard(ImplicitlyCopyable): var name: String var suppress_errors: Bool def __init__(out self, name: String, suppress_errors: Bool = False): self.name = name self.suppress_errors = suppress_errors def __enter__(self) -> Self: print("Acquiring:", self.name) return self def __exit__(self): print("Releasing:", self.name, "(no error)") def __exit__[ErrType: AnyType](self, err: ErrType) -> Bool: comptime type_name = reflect[ErrType].name() print("Releasing:", self.name) print(" Error type:", type_name) comptime if conforms_to(ErrType, Writable): print(" Message:", err) return self.suppress_errors ``` When no error occurs, `__exit__(self)` runs as usual. When a typed error occurs, `__exit__[ErrType]()` runs instead, giving you access to the error type and its data: ```mojo # No error — calls __exit__(self) with ResourceGuard("database"): print("Working...") # Typed error, suppressed — __exit__[ErrType] returns True with ResourceGuard("cache", suppress_errors=True): use_connection() # raises ConnectionError print("Continued after suppressed error") ``` ```output Acquiring: database Working... Releasing: database (no error) Acquiring: cache Releasing: cache Error type: ConnectionError Message: ConnectionError: connection timed out Continued after suppressed error ``` :::note For a complete working example including error suppression and propagation, see [`resource_guard.mojo`](https://github.com/modular/modular/blob/mojo/v1.1.0/Mojo/docs/site/code/manual/errors/resource_guard.mojo). ::: --- ## Closures :::caution Evolving feature Closures are a longstanding part of Mojo, used throughout the standard library and kernel infrastructure. Mojo's updated capture-list syntax is now available. Code examples on this page reflect the redesigned compiler behavior. ::: A *closure* is a function bundled together with values from its surrounding scope. You define it in one place and pass it somewhere else to run. The closure carries captured data with it. The compiler transforms it to a type with both behavior and storage. The code executing the closure doesn't need to know where that data came from. This allows closures to carry state and configuration into code that executes later or elsewhere. Configuration uses capture conventions to specify how the closure interacts with captured values, choosing between read-only references, mutable references, copies, or moves. Mojo closures look like nested functions, but they use a special syntax. Curly braces after the argument list form a *capture list* that declares which outer values the closure captures and how it interacts with them. The capture list is what distinguishes a closure from an ordinary nested function. It gives the compiler the information needed to manage captured values safely and eliminate ambiguity. When you specify how a value is captured, the compiler can enforce correct usage: ```mojo def main(): var multiplier = 3 def scale(x: Int) {imm multiplier} -> Int: return x * multiplier print(scale(5)) # 15 ``` `scale` is a closure. It captures `multiplier` from the enclosing scope using its capture list (`{imm multiplier}`). When you call `scale(5)`, the closure multiplies `5` by the captured value of `multiplier` and returns `15`. Without a capture list, the inner function can't see anything outside its own arguments. ## Why closures matter Closures package behavior together with the data that behavior needs. You define the work in one place, then pass that package for execution later, elsewhere, or on different hardware. This separation between defining work and executing work is central to how Mojo expresses computation. ## The capture list A capture list such as `{imm x, mut y}` tells the compiler how the closure captures and uses values from the surrounding scope. For this list, `x` is captured as an immutable reference. The closure can read it but can't modify it. `y` is captured as a mutable reference, so the closure can modify it and those changes are visible in the outer scope. Mojo requires captures to be explicit. In a systems language, knowing exactly which values a closure holds, and whether it reads, copies, or takes ownership of them, matters for both performance and correctness. ## Capture by immutable reference: `imm` Use `imm` when the closure needs to see a value but not change it: ```mojo def main(): var threshold = 100 def is_over(x: Int) {imm threshold} -> Bool: return x > threshold print(is_over(50)) # False print(is_over(200)) # True ``` The closure reads an immutable reference to `threshold`. It sees the current value each time it's called, including changes made after the closure was created: ```mojo def main(): var limit = 10 def check(x: Int) {imm limit} -> Bool: return x < limit print(check(5)) # True limit = 3 print(check(5)) # False (sees updated limit) ``` Because `imm` captures a reference, the closure reflects the live state of the original value. To capture every used outer value by immutable reference without naming each one, use `{imm}`: ```mojo def main(): var a = 1 var b = 2 def sum_ab() {imm} -> Int: return a + b print(sum_ab()) # 3 ``` ## Capture by mutable reference: `mut` Use `mut` when a closure needs to modify a captured value and make those changes visible in the enclosing scope: ```mojo def main(): var total = 0 def accumulate(x: Int) {mut total}: total += x accumulate(10) accumulate(20) print(total) # 30 ``` Changes to `total` inside the closure modify the original variable directly. This is a mutable reference, not a copy. The implicit form `{mut}` captures every used outer value with a mutable reference: ```mojo def main(): var count = 0 var items = List[String]() def record(name: String) {mut}: items.append(name) count += 1 record("alpha") record("beta") print(count) # 2 print(items) # ['alpha', 'beta'] ``` ## Capture by copy: `var` Use `var` when the closure needs its own independent copy of a value. Changes to the original don't affect the closure, and changes inside the closure don't affect the original. ```mojo def main(): var snapshot_val = 42 def frozen() {var snapshot_val} -> Int: return snapshot_val snapshot_val = 999 print(frozen()) # 42 (captured the value at definition time) ``` The closure copied `snapshot_val` when it was created. Later changes to `snapshot_val` in the outer scope don't affect the closure's copy. The implicit form `{var}` copies every used outer value: ```mojo def main(): var x = 10 var y = 20 def snap() {var} -> Int: return x + y x = 0 y = 0 print(snap()) # 30 (uses copied values) ``` :::note Copy captures call the value's copy initializer at the point where the closure is defined. For types with expensive copies (large lists, strings with allocations), prefer `imm` or `mut` when you don't need an independent copy. ::: ## Move capture: `var name^` Use `var name^` to transfer ownership of a value into the closure. The closure consumes the outer binding, which can't be used after the closure is created: ```mojo def main(): var data: List[Int] = [1, 2, 3] def take_data() {var data^}: print(data) take_data() # [1, 2, 3] # data can't be used here: ownership transferred to the closure # print(data) # Uncomment for error: 'data' is uninitialized after move ``` Move capture avoids a copy entirely. The value moves into the closure's storage. This is useful for types that are expensive to copy or for transferring unique ownership. :::note The `^` transfer operator only works with `var` or a bare name in capture lists. `{mut name^}`, `{ref name^}`, and `{imm name^}` are compiler errors. ::: ## Copyable closures: `var^` The `{var^}` capture list moves all referenced outer values into the closure. When the captured types are `Copyable`, the closure value also becomes copyable. This allows the closure itself to be assigned to new variables or passed by value. ```mojo def main(): var label = "sensor-1" def tag() {var^} -> String: return label var also_tag = tag # copies the closure (and its captures) print(tag()) # sensor-1 print(also_tag()) # sensor-1 ``` :::note When you copy a closure created with `{var^}`, each captured value is copied again through its copy initializer. For closures that capture large or expensive values, be aware of the cost. ::: Without `{var^}`, closures can't be assigned to new variables or copied. ## Caller-determined mutability: `ref` Use `{ref name}` when the closure's mutability depends on the caller's context. If the caller provides a mutable reference, the closure captures mutably. If immutable, the closure captures immutably. The following example uses `comptime if origin_of(items).mut` to inspect how the closure captures the value at each call site: ```mojo def show_mutability(ref items: List[Int]): def report() {ref items}: comptime if origin_of(items).mut: print("mut") else: print("immut") report() # Show immutability: `xs` uses the default `imm` argument convention def from_imm(xs: List[Int]): show_mutability(xs) # xs is an immutable reference here # Show mutability: `xs` uses the `mut` argument convention def from_mut(mut xs: List[Int]): show_mutability(xs) # xs is a mutable reference here def main(): var nums: List[Int] = [10, 20, 30] from_imm(nums) # immut from_mut(nums) # mut ``` `{ref name}` doesn't choose a mutability. It shares the captured `name`'s existing origin. The mutability is whatever that origin already carries, decided wherever `name` was bound. This is often the function's own `ref` parameter, ultimately resolved at the call site. :::note `ref` captures are an advanced feature for writing parameterized code that works across mutability contexts. For most closures, `imm` or `mut` is the right choice. ::: ## Empty capture list: `{}` An empty capture list means the closure uses nothing from its surrounding scope. It's a plain function that happens to be defined inside another function: ```mojo def main(): def doubled(x: Int) {} -> Int: return x * 2 print(doubled(5)) # 10 ``` The body may only use its own arguments. Referencing any outer value is a compile error: ```mojo # This example doesn't compile def main(): var a = 42 def wrong() {}: print(a) # error: no capture convention for 'a' ``` ## Mixing capture conventions A capture list is a comma-separated sequence of independent entries. Each entry specifies its own convention, and conventions don't carry over from one entry to the next. ```mojo def main(): var config = "prod" var count = 0 var label = "run-1" def process() {imm config, mut count, var label}: count += 1 print(config, count, label) process() # prod 1 run-1 label = "run-2" process() # prod 2 run-1 (label was copied at definition time) ``` Each entry is self-contained: `imm config` is a read-only reference, `mut count` is a mutable reference, and `var label` is a copy. A bare name without a convention keyword defaults to `imm`: ```mojo def main(): var x = 10 def show() {x}: # same as {imm x} print(x) show() # 10 ``` ### Setting a default convention A convention list can mix implicit and explicit entries, such as `{imm, mut count, var label}`. You may use at most one implicit entry per capture list, and you can place the entries in any order. `{mut count, var label, imm}` is equivalent to `{var label, imm, mut count}`. For example: ```mojo def main(): var a = 1 var b = 2 var z = "snapshot" def mixed() {mut, var z}: a += 10 b += 20 print(a, b, z) mixed() # 11 22 snapshot z = "changed" mixed() # 21 42 snapshot # z was copied at def-time # Changes to outer z don't reach the closure ``` ## Closures in practice ### Configurable behavior Closures let you build specialized behavior from general-purpose parts. The following parameterized function accepts any callable with the expected signature. The closure carries the configuration: ```mojo # `G` matches any `def(String) -> None` callable def greet_all[G: def(String) -> None](names: List[String], greet: G): for n in names: greet(n) def main(): var names: List[String] = ["Alice", "Bob"] var greeting = "Hello" def greeter(name: String) {imm greeting}: print(greeting + ", " + name + "!") greet_all(names, greeter) # Hello, Alice! # Hello, Bob! greeting = "Hi" greet_all(names, greeter) # Hi, Alice! # Hi, Bob! ``` The inner function `greeter` captures `greeting` as an immutable read-only reference. `greet_all` doesn't know anything about the greeting itself. It only knows how to call a function with the type `def(String) -> None`. Because the closure captures `greeting` by reference instead of by copy, changes to `greeting` between calls are visible inside the closure. ### Accumulating state Closures with `mut` captures can build up results across multiple calls. ```mojo def main(): var log = List[String]() def record(event: String) {mut log}: log.append(event) record("started") record("processed item") record("finished") for entry in log: print(entry) # started # processed item # finished ``` The closure `record` mutates `log` in the outer scope. Each call appends to the same list without passing it as an argument. --- ## Lambda expressions A **lambda function** is a small, anonymous function. Like a named function, it accepts arguments, returns a value, and can capture values from the surrounding scope. The difference is that you can write a lambda exactly where it's used instead of giving it a separate declaration. Lambdas are most useful when an algorithm stays the same but one small piece of its behavior changes. A transformation needs to know how to convert values. A sort needs to know how to compare them. A validator needs to decide whether a value satisfies a rule. A callback needs to know what to do when another part of the program invokes it. You could write a separate named function for each of these needs, but when the behavior is short and used in only one place, a lambda keeps it next to the algorithm that uses it. The algorithm stays easy to read, and the behavior doesn't need a permanent name. ## Creating a lambda A lambda expression looks like a function declaration without a name: ```mojo lambda (x: Int) -> Int: x + 1 ``` The body is always a single expression. When you call the lambda, the expression is evaluated and its result returned: ```mojo var inc = lambda (x: Int) -> Int: x + 1 print(inc(4)) # 5 ``` They're the same behavior you'd expect from a named function with less ceremony: ```mojo def inc(x: Int) -> Int: return x + 1 print(inc(4)) # 5 ``` ### Inline lambdas A lambda doesn't have to be assigned to a variable. You can write it directly as an argument when a function needs a small piece of custom behavior: ```mojo transform( lambda (x: Int) -> Int: x + 1, values ) ``` Inline lambdas work best when behavior is short and obvious. As they grow, assigning them to a local variable often makes the surrounding code easier to read. When passing lambdas to other functions, you bind the lambda as an argument. This allows the function to call it wherever that behavior is needed. It can also pass the lambda to other functions or use it in recursive calls. For example, a search algorithm can carry a lambda through recursion to provide lightweight, customizable pattern matching. ### Using lambdas for side effects You can use lambdas to call a function for each element in a collection without producing a result. This is useful for side effects, such as updating state. Lambda return types are optional. When omitted, they default to None: ```mojo # `histogram`, defined in a later example, is a dictionary of counts var collector = lambda (n: Int) {mut histogram}: increment(histogram, n) apply(collector, counts) ``` ## Higher-order functions Higher-order functions separate algorithms from custom logic supplied by the caller. When you scaffold an algorithm, you can delegate the parts that change to the caller. Lambda expressions are the perfect way to define that behavior. Imagine you're converting a collection of values to a new type. The algorithm knows how to visit every element, build a new collection, and return the result. It doesn't know how each value should be converted. Using a lambda lets you customize behavior at the callsite without changing the transformation function. You choose any effect so long as the shape of the lambda matches: ```mojo # Convert an Int to c_int lambda (value: Int) -> c_int: c_int(value) # Double an Int value lambda (value: Int) -> Int: value * 2 ``` All higher-order functions in Mojo share one thing in common: they accept functions as arguments, not parameters, using infer-only typing. ### Bubble sort Lambdas make it easy to wrap a comparator. Consider this bubble sort implementation. `F` describes the shape of a user-supplied comparison function: ```mojo def bubble_sort[ T: ImplicitlyCopyable & Deinitable, F: def(T, T) -> Bool, // ](compare_fn: F, mut values: List[T]): for end in reversed(range(len(values))): for i in range(end): if compare_fn(values[i], values[i + 1]): values[i], values[i + 1] = values[i + 1], values[i] ``` You can define a free function to compare values, and pass them to `bubble_sort`: ```mojo def ascending(x: Int, y: Int) -> Bool: return x > y def main(): var values: List[Int] = [3, 1, 4, 1, 5, 9] bubble_sort(ascending, values) # [1, 1, 3, 4, 5, 9] ``` Or, you can write the comparison inline with a lambda: ```mojo bubble_sort(lambda (a: Int, b: Int) -> Bool: a > b, values) ``` It's the same result, with no function declaration. This is a key lambda feature. You can write behavior without giving it a name or exposing it through a permanent API, and your algorithm can use it immediately. Flip the comparison from greater-than to less-than to sort in the opposite order. ## Thin lambdas and parameters A thin lambda carries no state. That means, it won't capture values from the surrounding scope and it doesn't declare a compile-time parameter list with unbound values. It's just a one-expression function written in-line. Consider this transformation function. It uses a thin function pointer parameter to transform each element of a list: ```mojo def inplace_transform[ T: ImplicitlyCopyable & Deinitable, //, f: def(T) thin -> T ](mut list: List[T]): for index in range(len(list)): list[index] = f(list[index]) ``` Notice how the function pointer is passed as a parameter and declared with the `thin` effect. You can call `inplace_transform` with a simple algorithm to double each value: ```mojo def main(): var numbers: List[Int] = [1, 2, 3, 4, 5] inplace_transform[lambda (x: Int) -> Int: x * 2](numbers) print(t"transformed numbers: {numbers}") # [2, 4, 6, 8, 10] ``` This example works because the lambda doesn't capture state, so it isn't a *closure*. Contrast this with the next example, which won't compile: ```mojo var factor = 3 inplace_transform[lambda (x: Int) -> Int: x ** factor](numbers) ``` `factor` is declared in the same scope as the lambda, and the lambda captures it. This one thing makes the lambda a closure and can't be used at compile-time as the function pointer parameter needed by `inplace_transform()`. ## Lambdas and closures Lambdas that capture values from the surrounding scope are closures. Instead of passing values into the lambda, the closure retrieves them from the surrounding context, allowing you to write more concise code. You specify the convention used to capture and manipulate values in the lambda expression. When not specified, this defaults to immutable references (`imm`). You can read more about closure conventions in the [Mojo language reference](/docs/reference/closure-declarations/#capture-conventions). There are two ways to use lambda closures: direct calls, and runtime arguments. ### Using closures with direct calls In this example, the lambda closure captures `x` and `y` from the surrounding scope. The lambda is called immediately, and the result is returned. Updating the values of `x` and `y` and calling the lambda again returns a new result: ```mojo var x, y = 3.0, 4.5 var magnitude = ( lambda -> Float64: (x**2 + y**2) ** 0.5 ) var distance = magnitude() print(t"distance of ({x}, {y}): {distance}") # 5.408326913175031 x, y = -2.5, 1.5 distance = magnitude() print(t"distance of ({x}, {y}): {distance}") # 2.9154759474226504 ``` ### Runtime arguments Runtime arguments can accept both thin lambdas and closures. Here's a `transform()` function that uses a runtime argument, with an infer-only function type: ```mojo def transform[ T: Copyable, U: Copyable, F: def(T) -> U, // ](f: F, list: List[T]) -> List[U]: return [f(item) for item in list] ``` In the preceding section, the following lambda closure wouldn't compile because it was passed at compile-time to a parameter, which doesn't accept closures. `transform()` uses a runtime function argument. Now, the code compiles and runs: ```mojo var numbers: List[Int] = [2, 4, 6, 8, 10] var factor = 3 var transformed = transform(lambda (x: Int) -> Int: x**factor, numbers) print(t"transformed numbers: {transformed}") # [8, 64, 216, 512, 1000] factor = 2 transformed = transform(lambda (x: Int) -> Int: x**factor, numbers) print(t"transformed numbers: {transformed}") # [4, 16, 36, 64, 100] ``` ## Capturing and mutating state A closure can update the values it captures without mentioning those values in its own code. This next example showcases lambdas to create a histogram of word lengths. The code starts with an `apply()` function. It calls a function for each member of a list. As you can see from its function type (`F`), it takes lambdas that don't return a value. The lambda is called for its side effects, not for the value of its expression: ```mojo def apply[T: Copyable, F: def(T) -> None, //](f: F, i: List[T]): for item in i: f(item) ``` In this example, a closure will update a captured histogram dictionary. It does this by calling `increment[]()`. This function updates a dictionary by increasing the value for a given key by one: ```mojo def increment[ Key: ImplicitlyCopyable & Hashable & Equatable & Deinitable ](mut d: Dict[Key, Int], key: Key): d[key] = d.get(key, 0) + 1 ``` This example is given a string of words. It removes punctuation and splits the string into a word list. Then, it counts the length of each word using a lambda: ```mojo var words = String( "Lorem ipsum dolor sit amet, consectetur adipiscing elit. " "Sed fringilla nons sapien quis pharetra." ).replace(",", "").replace(".", "") var word_list = [String(w) for w in words.split(" ")] print(t"word_list: {word_list}") # Count each word var counter = lambda (x: String) -> Int: x.count_codepoints() var counts = transform(counter, word_list) print(t"Initial counts: {counts}") # [5, 5, 5, 3, 4, 11, 10, 4, 3, 9, 4, 6, 4, 8] ``` To build the histogram, the next lambda captures the histogram dictionary and uses `increment[]()` to update the count for each word length: ```mojo # Create a histogram of the counts var histogram: Dict[Int, Int] = {} var collector = lambda (n: Int) {mut histogram}: increment(histogram, n) apply(collector, counts) print(t"Histogram: {histogram}") # {5: 3, 3: 2, 4: 4, 11: 1, 10: 1, 9: 1, 6: 1, 8: 1} ``` The `mut` capture establishes that the lambda can mutate the captured variable. `apply()` calls this lambda for each word length, updating the histogram dictionary as it goes. A final lambda transforms each count into stars. It's the kind of effortless transformation that makes lambdas so useful: ```mojo var stars = lambda (n: Int) -> String: "*" * n for key in histogram.keys(): print(t"{key}: {stars(histogram.get(key, 0))}") ``` ## Lambdas and FFI interop Thin lambdas work well with C FFI. This final example uses a lambda to sort a list of integers using the C standard library `qsort()` function. The lambda is passed as the comparator to `qsort()` to sort the list in ascending order: ```mojo from std.ffi import external_call, c_int, c_size_t from std.sys import size_of def main(): var values: List[Int] = [5, 3, 11, 10, 9, 6, 4] # Transform is defined earlier on this page var c_values: List[c_int] = transform( lambda (v: Int) -> c_int: c_int(v), values ) external_call["qsort", NoneType]( c_values.unsafe_ptr(), # values are passed as an opaque pointer c_size_t(len(c_values)), c_size_t(size_of[c_int]()), lambda ( a: MutOpaquePointer[MutUntrackedOrigin], b: MutOpaquePointer[MutUntrackedOrigin], ) abi("C") -> c_int: a.unsafe_bitcast[c_int]()[] - b.unsafe_bitcast[c_int]()[] ) # Bitcasting retrieves the `c_int` values from the opaque pointer print(t"Sorted keys: {c_values}") # [3, 4, 5, 6, 9, 10, 11] ``` Don't miss first lambda call shown in this example. It converts a list of integers to a list of `c_int` values with `transform()` before passing them to "qsort". --- ## Functions Mojo uses the `def` keyword to define functions. ## Anatomy of a function A Mojo function declaration can include the following elements:
def function_name[
​    parameters ...
](
​    arguments ...
) -> return_value_type:
​    function_body
Functions can have: - Parameters: A function can optionally take one or more compile-time _parameter_ values used for metaprogramming. - Arguments: A function can also optionally take one or more run-time _arguments_. - Return value: A function can optionally return a value. - Function body: Statements that run when you call the function. Function definitions must include a body. You can omit all of the optional parts of the function, so the minimal function is something like this: ```mojo def do_nothing(): pass ``` If a function takes no parameters, you can omit the square brackets, but the parentheses are always required. Although you can't leave out the function body, you can use the `pass` statement to define a function that does nothing. :::note Struct methods Functions declared inside a struct are called _methods_. They can include the same elements as regular functions, but follow a few extra rules. For more information, see the page on [structs](/docs/manual/structs/). ::: ### Arguments and parameters Functions take two kinds of inputs: _arguments_ and _parameters_. Arguments are familiar from many other languages: they are run-time values passed into the function. ```mojo def max(a: Int, b: Int) -> Int: return a if a > b else b ``` On the other hand, you can think of a parameter as a compile-time variable that becomes a run-time constant. For example, consider the following function with a parameter: ```mojo no-test def add_tensors[rank: Int](a: MyTensor[rank], b: MyTensor[rank]) -> MyTensor[rank]: # ... ``` In this case, the `rank` value needs to be specified in a way that can be determined at compilation time, such as a literal or expression. When you compile a program that uses this code, the compiler produces a unique version of the function for each unique `rank` value used in the program, with `rank` treated as a constant within each specialized version. This usage of "parameter" is probably different from what you're used to from other languages, where "parameter" and "argument" are often used interchangeably. In Mojo, "parameter" and "parameter expression" refer to compile-time values, and "argument" and "expression" refer to run-time values. By default, both arguments and parameters can be specified either by position or by keyword. These forms can also be mixed in the same function call. ```mojo # positional var x = max(5, 7) # Positionally, a=5 and b=7 # keyword var y = max(b=3, a=9) # mixed var z = max(5, b=7) # Positionally, a=5 ``` For more information on arguments, see [Function arguments](#function-arguments) on this page. For more information on parameters, see [Parameterization: compile-time metaprogramming](/docs/manual/parameters/). ## Function requirements A function has the following requirements: - You must declare the type of each function parameter and argument. - If a function doesn't return a value, you can either omit the return type or declare `None` as the return type. ```mojo no-test # The following function definitions are equivalent def greet(name: String): print("Hello,", name) def greet(name: String) -> None: print("Hello,", name) ``` - If the function returns a value, you must either declare the return type using the -> type syntax or provide a [named result](#named-results) in the argument list. ```mojo no-test # The following function definitions are equivalent def incr(a: Int) -> Int: return a + 1 def incr(a: Int, out b: Int): b = a + 1 ``` For more information, see the [Return values](#return-values) section of this page. ## Function arguments :::note Functions with / and * in the argument list You might see the following characters in place of arguments: slash (`/`) and/or star (`*`). For example: ```mojo no-test def myfunc(pos_only, /, pos_or_keyword, *, keyword_only): ``` Arguments **before** the `/` can be passed only by position. Arguments **after** the `*` can be passed only by keyword. For details, see [Positional-only and keyword-only arguments](#positional-only-and-keyword-only-arguments) You may also see argument names prefixed with one or two stars (`*`): ```mojo no-test def myfunc2(*names, var **attributes): ``` An argument name prefixed by a single star character, like `*names` identifies a [variadic argument](#variadic-arguments), while an argument name prefixed with a double star, like `**attributes` identifies a [variadic keyword-only argument](#variadic-keyword-arguments). ::: ### Optional arguments An optional argument is one that includes a default value, such as the `exp` argument here: ```mojo def my_pow(base: Int, exp: Int = 2) -> Int: return base**exp def use_defaults(): # Uses the default value for `exp` var z = my_pow(3) print(z) ``` However, you can't define a default value for an argument that's declared with the [`mut`](/docs/manual/values/ownership/#mutable-arguments-mut) argument convention. Any optional arguments must appear after any required arguments. [Keyword-only arguments](#positional-only-and-keyword-only-arguments), discussed later, can also be either required or optional. ### Keyword arguments You can also use keyword arguments when calling a function. Keyword arguments are specified using the format argument_name = argument_value. You can pass keyword arguments in any order: ```mojo duplicate-of=optional-args def my_pow(base: Int, exp: Int = 2) -> Int: return base**exp def use_keywords(): # Uses keyword argument names (with order reversed) var z = my_pow(exp=3, base=2) print(z) ``` ### Variadic arguments Variadic arguments let a function accept a variable number of arguments. To define a function that takes a variadic argument, use the variadic argument syntax *argument_name: ```mojo def sum(*values: Int) -> Int: var sum: Int = 0 for value in values: sum = sum + value return sum ``` The variadic argument `values` here is a placeholder that accepts any number of passed positional arguments. You can define zero or more arguments before the variadic argument. When calling the function, Mojo assigns any remaining positional arguments to the variadic argument, so any arguments declared **after** the variadic argument can only be specified by keyword (see [Positional-only and keyword-only arguments](#positional-only-and-keyword-only-arguments)). Variadic arguments fall into two categories: - Homogeneous variadic arguments, where all of the passed arguments are the same type—all [`Int`](/docs/std/simd/#int), or all [`String`](/docs/std/collections/string/string/String/), for example. - Heterogeneous variadic arguments, which can accept a set of different argument types. The following sections describe how to work with homogeneous and heterogeneous variadic arguments. :::note Variadic parameters Mojo also supports variadic _parameters_, but with some limitations—for details see [variadic parameters](/docs/manual/parameters/#variadic-parameters). ::: #### Homogeneous variadic arguments When defining a homogeneous variadic argument (all arguments must be the same type), use *argument_name: argument_type: ```mojo def greet(*names: String): ... ``` Inside the function body, the variadic argument is available as an iterable list for ease of use. Concretely, that type is named [`VariadicList`](/docs/std/builtin/variadics/VariadicList/). Here is a simple example: ```mojo def sum(*values: Int) -> Int: var sum: Int = 0 for value in values: sum = sum + value return sum ``` Iterating over this list directly with a `for..in` loop currently produces a reference to the element, which can be mutable with a `mut` variadic list. Use the `ref` binding pattern to capture a mutable reference if you want to mutate the elements of the list: ```mojo def make_worldly(mut *strs: String): for ref i in strs: i += " world" ``` You can also directly index the list with integers as well: ```mojo def make_worldly2(mut *strs: String): for i in range(len(strs)): strs[i] += " world" ``` #### Heterogeneous variadic arguments Implementing heterogeneous variadic arguments (each argument type may be different) is somewhat more complicated than homogeneous variadic arguments. To handle multiple argument types, the function must be [parameterized](/docs/manual/generics/), which requires using [traits](/docs/manual/traits/) and [parameters](/docs/manual/parameters/). So the syntax may look a little unfamiliar if you haven't worked with those features. The signature for a function with a heterogeneous variadic argument looks like this: ```mojo no-test def count_many_things[*ArgTypes: Intable](*args: *ArgTypes): ... ``` The parameter list, `[*ArgTypes: Intable]` specifies that the function takes an `ArgTypes` parameter, which is a list of types, all of which conform to the [`Intable`](/docs/std/builtin/int/Intable/) trait. The asterisk in `*ArgTypes` indicates that `ArgTypes` is a **variadic type parameter** (a list of types). The argument list, `(*args: *ArgTypes)` has the familiar `*args` for the variadic argument, but instead of a single type, its type is defined as the variadic type list `*ArgTypes`. The asterisk in `*args` indicates a **variadic argument**, and the asterisk in `*ArgTypes` refers to the variadic type parameter. This means that each argument in `args` has a corresponding type in `ArgTypes`, so args[n] is of type ArgTypes[n]. Inside the function, `args` becomes a [`VariadicPack`](/docs/std/builtin/variadics/VariadicPack/) because the syntax `*args: *ArgTypes` creates a heterogeneous variadic argument. That means each element in `args` can be a different type that requires a different amount of memory. To iterate through the `VariadicPack`, the compiler must know each element's type, so you must use a [`comptime for` loop](/docs/manual/metaprogramming/comptime-evaluation/#comptime-for): ```mojo def count_many_things[*ArgTypes: Intable](*args: *ArgTypes) -> Int: var total = 0 comptime for i in range(args.__len__()): total += Int(args[i]) return total def main(): print(count_many_things(5, 11.7, 12)) # 28 ``` Notice that when calling `count_many_things()`, you don't actually pass in a list of argument types. You only need to pass in the arguments, and Mojo generates the `ArgTypes` list itself. #### Variadic keyword arguments Mojo functions also support variadic keyword arguments (`**kwargs`). Variadic keyword arguments let you pass an arbitrary number of keyword arguments. To define a function that takes a variadic keyword argument, use the variadic keyword argument syntax var **kw_argument_name: ```mojo def print_nicely(var **kwargs: Int): for item in kwargs.items(): print(item.key, "=", item.value) ``` Calling it with any number of keyword arguments prints each one: ```mojo # prints: # `a = 7` # `y = 8` print_nicely(a=7, y=8) ``` In this example, the argument name `kwargs` is a placeholder that accepts any number of keyword arguments. Inside the body of the function, you can access the arguments as a dictionary of keywords and argument values (specifically, an instance of [`StringDict`](/docs/std/collections/dict/StringDict/)). There are currently a few limitations: - Variadic keyword arguments must be declared with the `var` [argument convention](/docs/manual/values/ownership#argument-conventions) (the function owns the argument dictionary and may mutate it); no other convention is supported: ```mojo no-test # Not supported. def imm_kwargs(imm **kwargs: Int): ... ``` - All the variadic keyword arguments must have the same type, and this determines the type of the argument dictionary. For example, if the argument is `var **kwargs: Float64` then the argument dictionary is a `StringDict[Float64]`. - The argument type must conform to the [`Copyable`](/docs/std/traits/copyable/Copyable/) trait. - Dictionary unpacking isn't supported yet: ```mojo no-test def takes_dict(d: Dict[String, Int]): print_nicely(**d) # Not supported yet. ``` - Variadic keyword _parameters_ aren't supported yet: ```mojo no-test # Not supported yet. def var_kwparams[**kwparams: Int](): ... ``` ### Positional-only and keyword-only arguments When defining a function, you can restrict some arguments so that they can be passed only as positional arguments, or they can be passed only as keyword arguments. To define positional-only arguments, add a slash character (`/`) to the argument list. Any arguments before the `/` are positional-only: they can't be passed as keyword arguments. For example: ```mojo def min(a: Int, b: Int, /) -> Int: return a if a < b else b ``` This `min()` function can be called with `min(1, 2)` but can't be called using keywords, like `min(a=1, b=2)`. There are several reasons you might want to write a function with positional-only arguments: - The argument names aren't meaningful for the caller. - You want the freedom to change the argument names later on without breaking backward compatibility. For example, in the `min()` function, the argument names don't add any real information, and there's no reason to specify arguments by keyword. For more information on positional-only arguments, see [PEP 570 – Python Positional-Only Parameters](https://peps.python.org/pep-0570/). Keyword-only arguments are the inverse of positional-only arguments: they can be specified only by keyword. If a function accepts variadic arguments, any arguments defined _after_ the variadic arguments are treated as keyword-only. For example: ```mojo def sort(*values: Float64, ascending: Bool = True): ... ``` In this example, you can pass any number of [`Float64`](/docs/std/simd/#float64) values, optionally followed by the keyword `ascending` argument: ```mojo sort(1.1, 6.5, 4.3, ascending=False) ``` If the function doesn't accept variadic arguments, you can add a single star (`*`) to the argument list to separate the keyword-only arguments: ```mojo def kw_only_args(a1: Int, a2: Int, *, double: Bool) -> Int: var product = a1 * a2 if double: return product * 2 else: return product ``` Keyword-only arguments often have default values, but this isn't required. If a keyword-only argument doesn't have a default value, it's a _required keyword-only argument_. It must be specified, and it must be specified by keyword. Any required keyword-only arguments must appear in the signature before any optional keyword-only arguments. That is, arguments appear in the following sequence in a function signature: - Required positional arguments. - Optional positional arguments. - Variadic arguments. - Required keyword-only arguments. - Optional keyword-only arguments. - Variadic keyword arguments. For more information on keyword-only arguments, see [PEP 3102 – Keyword-Only Arguments](https://peps.python.org/pep-3102/). ## Overloaded functions All function declarations must specify argument types, so if you want a function to work with different data types, you need to implement separate versions of the function that each specify different argument types. This is called "overloading" a function. For example, here's an overloaded `add()` function that can accept either `Int` or `String` types: ```mojo def add(x: Int, y: Int) -> Int: return x + y def add(x: String, y: String) -> String: return x + y ``` If you pass anything other than `Int` or `String` to the `add()` function, you'll get a compiler error. That is, unless `Int` or `String` can implicitly cast the type into their own type. For example, `String` includes an overloaded version of its initializer (`__init__()`) that supports [implicit conversion](/docs/manual/lifecycle/life/#constructors-and-implicit-conversion) from a [`StringLiteral`](/docs/std/builtin/string_literal/StringLiteral/) value. Thus, you can also pass a `StringLiteral` to a function that expects a `String`. When resolving an overloaded function call, the Mojo compiler picks the candidate that best fits the call according to the rules in [Overload resolution](#overload-resolution), or reports the call as ambiguous if no single candidate is best. :::note Overload sets An "overload set" is a collection of function overloads that share the same name but different signatures. - Overload sets can't be extended by imports, aliases, or parameters. - To avoid issues with local functions using the same name as an imported one, use an aliased import. `from package import foo as imported_foo` won't conflict with a local function named `foo`. ::: ### Overload resolution When resolving an overloaded function, Mojo looks at: - The number, position, and keyword of each argument. - The type of each argument and each compile-time parameter. - The argument conventions on each argument. - Whether the candidate is an instance method or [static method](/docs/manual/structs/#static-methods). - Whether an initializer allows [implicit conversion](/docs/manual/lifecycle/life/#constructors-and-implicit-conversion). Mojo does **not** look at the return type, the `raises` effect, or any context surrounding the call. Two functions that differ only in return type or in whether they `raises` are duplicate definitions—the compiler rejects the second declaration. The overload resolution logic filters for candidates according to the following rules, in order of precedence: 1. Candidates requiring the smallest number of implicit conversions (in both arguments and parameters). 2. Candidates without variadic arguments. 3. Candidates without variadic parameters. 4. Candidates with the shortest parameter signature. 5. Non-`@staticmethod` candidates (over `@staticmethod` ones, if available). If the compiler can't figure out which function to use, you can resolve the ambiguity by explicitly casting your value to a supported argument type. For example, the following code calls the overloaded `foo()` function, but both implementations accept an argument that supports [implicit conversion](/docs/manual/lifecycle/life#constructors-and-implicit-conversion) from `String`. So, the call to `foo("Hello")` is ambiguous and creates a compiler error. You can fix this by casting the value to the type you really want: ```mojo struct MyString: @implicit def __init__(out self, string: String): pass struct YourString: @implicit def __init__(out self, string: String): pass def foo(name: MyString): print("MyString") def foo(name: YourString): print("YourString") def call_foo(): # Both `foo` overloads can accept `"Hello"`, so Mojo doesn't know # which one to call. foo(MyString("Hello")) ``` For the full overload-resolution rules and edge cases, see the [function declarations reference](/docs/reference/function-declarations/#function-overloads). ## Return values Return value types are declared in the signature using the -> type syntax. Values are passed using the `return` keyword, which ends the function and returns the identified value (if any) to the caller. ```mojo def get_greeting() -> String: return "Hello" ``` By default, the value is returned to the caller as an owned value. As with arguments, a return value may be [implicitly converted](/docs/manual/lifecycle/life#constructors-and-implicit-conversion) to the named return type. For example, the previous example calls `return` with a string literal, `"Hello"`, which is implicitly converted to a `String`. :::note Returning a reference A function can also return a mutable or immutable reference using a `ref` return value. For details, see [Lifetimes, origins, and references](/docs/manual/values/lifetimes/). ::: ### Named results Named function results allow a function to return a value that can't be moved or copied. Named result syntax lets you specify a named, uninitialized variable to return to the caller using the `out` argument convention: ```mojo def get_name_tag(var name: String, out name_tag: NameTag): name_tag = NameTag(name^) ``` The `out` argument convention identifies an uninitialized variable that the function must initialize. (This is the same as the `out` convention used in [struct initializers](/docs/manual/lifecycle/life/#constructor).) The `out` argument for a named result can appear anywhere in the argument list, but by convention, it should be the last argument in the list. A function can declare only one return value, whether it's declared using an `out` argument or using the standard -> type syntax. A function with a named result argument doesn't need to include an explicit `return` statement, as shown above. If the function terminates without a `return`, or at a `return` statement with no value, the value of the `out` argument is returned to the caller. If it includes a `return` statement with a value, that value is returned to the caller, as usual. The fact that a function uses a named result is transparent to the caller. That is, these two signatures are interchangeable to the caller: ```mojo no-test def get_name_tag(var name: String) -> NameTag: ... def get_name_tag(var name: String, out name_tag: NameTag): ... ``` In both cases, the call looks like this: ```mojo var tag = get_name_tag("Judith") ``` Because the return value is assigned to this special `out` variable, it doesn't need to be moved or copied when it's returned to the caller. This means that you can create a function that returns a type that can't be moved or copied, and which takes several steps to initialize: ```mojo struct ImmovableObject: var name: String def __init__(out self, var name: String): self.name = name^ def create_immovable_object(var name: String, out obj: ImmovableObject): obj = ImmovableObject(name^) obj.name += "!" # obj is implicitly returned ``` To the caller, it's an ordinary function call: ```mojo var my_obj = create_immovable_object("Blob") ``` By contrast, the following function with a standard return value doesn't work: ```mojo no-test def create_immovable_object2(var name: String) -> ImmovableObject: var obj = ImmovableObject(name^) obj.name += "!" return obj^ # Error: ImmovableObject is not copyable or movable ``` Because `create_immovable_object2` uses a local variable to store the object while it's under construction, the return call requires it to be either moved or copied to the callee. This isn't an issue if the newly-created value is returned immediately: ```mojo def create_immovable_object3(var name: String) -> ImmovableObject: return ImmovableObject(name^) # OK ``` ## Raising and non-raising functions By default, when a function raises an error, the function terminates immediately and the error propagates to the calling function. If the calling function doesn't handle the error, it continues to propagate up the call stack. ```mojo def raises_error() raises: raise Error("There was an error.") ``` Mojo functions are _non-raising_ by default. To declare that a function can propagate an error to its caller, add the `raises` keyword to the function signature. A non-raising function that calls a raising function **must handle any possible errors**. ```mojo no-test # This function will not compile def unhandled_error(): raises_error() # Error: can't call raising function in a non-raising context # Explicitly handle the error def handle_error(): try: raises_error() except e: print("Handled an error:", e) # Explicitly propagate the error def propagate_error() raises: raises_error() ``` All of the examples above use the built-in [`Error`](/docs/std/builtin/error/Error/) type. Mojo also supports _typed errors_, where you specify a custom error type a function can raise: ```mojo no-test def validate(value: Int) raises ValidationError -> Int: ... ``` For more information, see [Errors, error handling, and context managers](/docs/manual/errors/). --- ## Parameterized declarations Parameterized types let you write code once and use it across many types without duplicating logic. You don't need separate implementations or type checks for each case. Mojo generates specialized versions for each type you use. Most languages only parameterize over types. Mojo also supports value parameters. Its parameter system accepts both types and compile-time values using the same `[]` syntax. Mojo distinguishes compile-time parameters from runtime arguments in its syntax. Parameters go in square brackets `[]` and resolve at compile time. Arguments go in parentheses `()` and resolve at runtime. You see this distinction at every definition and call site, so you always know what the compiler specializes and what gets passed at runtime. ```mojo # T is a type parameter, threshold is a value parameter. # Both are compile-time. values is a runtime argument. def count_above[ T: Comparable & ImplicitlyCopyable & Deinitable, threshold: T ](values: List[T]) -> Int: var count = 0 for v in values: if v > threshold: count += 1 return count ``` Many parameterized declarations use traits to constrain which types work with the code. A trait defines what a type must do, and parameterized code declares which traits it requires. The compiler enforces these requirements and generates specialized code for each concrete type at the call site. ## Type parameters {#type-generics} Type parameters let you write code that works across many types. You define behavior once, and the compiler specializes it for each concrete type at the call site. ### Type constraints *Constraints* define what a type must do. You express them as traits or trait compositions. You must always constrain or explicitly type parameter names. Without a fixed set of required features, the compiler has no guarantees about what operations are valid. - The most permissive constraint is [`AnyType`](/docs/std/traits/anytype/AnyType/). It places no behavioral requirements on a type. - [`Deinitable`](/docs/std/traits/deinitable/Deinitable/) is a common baseline for types with lifetimes. Parameterized code that stores or owns values often requires it. Constraints make your code sound: every operation you use is guaranteed to exist for any type that satisfies them. ### Naming conventions Mojo follows naming conventions used in languages like Rust and C++. Type parameter names use PascalCase, short (`T`, `E`) or descriptive (`ErrorType`, `Element`). By convention, `T`, `U`, `V` are general types; `K`/`V` for key-value pairs; `E` for errors; `H` for hashers. Value parameter names use lower_snake_case and should be descriptive (`capacity`, `hasher`, `tile_x`). ### Basic example: compare two lists Consider comparing two lists to test whether they contain the same values in the same order. You could write a separate implementation for each element type. Or you can write one parameterized function that works for any list whose elements support the operations you need. This concrete version only works with integers: ```mojo def all_equal_int(ref lhs: List[Int], ref rhs: List[Int]) -> Bool: if len(lhs) != len(rhs): return False for left, right in zip(lhs, rhs): if left != right: return False return True ``` The parameterized version doesn't care about the element type. It only requires the capabilities the algorithm uses: elements must support equality comparison, be copyable, and be implicitly destructible (so the tuples `zip()` yields can be cleaned up at the end of each iteration): ```mojo def all_equal[ T: Equatable & Copyable & Deinitable ](ref lhs: List[T], ref rhs: List[T]) -> Bool: if len(lhs) != len(rhs): return False for left, right in zip(lhs, rhs): if left != right: return False return True ``` Both implementations follow the same logic: check lengths, return `False` on the first mismatch, and return `True` if no differences are found. The type parameter `T` is declared in square brackets before the function arguments. It represents the element type for both lists. When you call `all_equal()`, the compiler infers `T` from the call site: ```mojo print("Int (Expect True):\t", all_equal([1, 2, 3], [1, 2, 3])) # True print("Int (Expect False):\t", all_equal([1, 2, 3], [4, 5, 6])) # False print("String (Expect True):\t", all_equal(["hello", "world"], ["hello", "world"])) # True print("String (Expect False):\t", all_equal(["hello", "world"], ["goodbye", "world"])) # False ``` The compiler generates a concrete, type-specific version of `all_equal()` for each type you use: one for `Int` and one for `String` in this example. :::note In type theory, the parametric `all_equal()` function is *polymorphic*. The generated type-specific versions are *monomorphic*. ::: ### Choosing constraints Keep requirements minimal: ```mojo T: Equatable & Copyable & Deinitable ``` The ampersand (`&`) composes traits. Use the fewest constraints your code needs. This keeps your function usable with more types. If you remove `Equatable` from `all_equal()`, the code won't compile because the compiler can't guarantee that `!=` exists for all `T`. Dropping `Deinitable` fails for a subtler reason: `zip()` yields each pair as a tuple, and the loop can only destroy that temporary tuple if `T` is implicitly destructible. :::note A type supports `!=` by implementing `__ne__()`. ::: When your code uses an operation not covered by its constraints, the compiler reports an error: the type is *underspecified*. Fix this by adding the trait that provides the missing behavior. In practice, parameterized errors mean your constraints don't include the behavior your code uses. Adding constraints restricts which types you accept, but expands what your code can do. Each trait adds guaranteed operations, which lets the compiler check correctness and reason about lifetimes and effects. ### Parameterized types {#generic-parameter-types} In the `all_equal()` example, both parameters use the same element type: ```mojo lhs: List[T], rhs: List[T] ``` Using `T` for both ensures your loop compares like with like and prevents type mismatches. You can also use a parameterized type directly without embedding it in a container: ```mojo def my_parameterized_fn[T: AnyType](value: T): ``` This function accepts any type because its only limit is `AnyType`, the root of the trait hierarchy that all types conform to. Using `AnyType` means the value has no guaranteed deinitializer or lifetime management. Outside of [reflection](/docs/manual/metaprogramming/reflection/), this function can't do anything meaningful with `value`. #### Printing under-specified parameterized values A common issue with `AnyType` is that the compiler can't print values of unspecified types. Because it can't determine whether `T` conforms to `Writable`, it can't generate the code needed to print it: ```mojo def function[Ts: AnyType](*args: Ts): for arg in args: print(arg) # Will error. # The compiler can't verify `Writable` conformance def main(): function(1, 2, 3) ``` Work around this by testing the parameterized type parameter for `Writable` conformance and downcasting to expose the `Writable` trait: ```mojo def represent[T: AnyType](v: T) -> String: comptime if conforms_to(T, Writable): return String(v) else: return String(t"{reflect[T].name()}") def function[Ts: AnyType](*args: Ts): for arg in args: print(represent(arg)) @fieldwise_init struct SomeStruct: var x: Int def main(): function(1, 2, 3) # prints each integer function(SomeStruct(2), SomeStruct(3)) # each "(module-name).SomeStruct" ``` `Writable` items print as if explicitly converted to `String`. Non-`Writable` items use their type name, prefixed by the module name (the file name without the extension). Read more about [safe downcasting](#downcasting-safely) on this page. ## Parameterized types {#generic-types} Parameterized declarations aren't limited to functions. You can define parameterized types that use compile-time parameters to define both their fields and their methods. Parameterized types let you package a reusable shape: stored fields plus supported operations. Instead of writing `PairInt`, `PairString`, and so on, you write one `Pair[T]` and let the compiler generate specialized versions for each concrete `T` you use. ```mojo comptime ComparableValue = Equatable & ImplicitlyCopyable & Deinitable @fieldwise_init struct Pair[T: ComparableValue](ComparableValue): var left: Self.T var right: Self.T def __eq__(self, other: Pair[Self.T]) -> Bool: return self.left == other.left and self.right == other.right ``` Like parameterized functions, parameterized types use placeholder parameters, but a parameterized type uses those parameters in its storage as well as in its methods. Here, `Pair` stores two values of the same element type and implements equality by comparing its fields. `Pair` needs to compare values, so `T` must be equatable. It also needs to copy and clean up values in common operations, so it applies a trait composition on `T`: ```mojo comptime ComparableValue = Equatable & ImplicitlyCopyable & Deinitable ``` The `Pair` definition applies this conformance in two places: ```mojo struct Pair[T: ComparableValue](ComparableValue): ``` - **Square brackets** — a requirement on callers: any value used with `Pair` must have type `T`, and `T` must be a `ComparableValue`. - **Parentheses** — a promise from `Pair` itself: "I am a `ComparableValue`." This makes `Pair[T]` usable anywhere a `ComparableValue` is required, and ensures all `T` values conform. ## Mixing type and value parameters Parameterized types can take non-type parameters too. Add whatever you need to define the behavior: ```mojo struct ExampleStruct: def example[ T: Writable & Copyable, # type parameter count: Int, # value parameter ]( self, data: String, # argument init_value: T # parameterized argument ) -> String: ``` By convention, Mojo uses lower_snake_case for compile-time value parameters. This visually distinguishes them from type parameters. ## Simplified conformance syntax with `Some` You can replace explicit type parameters and conformances with concise `Some[Trait(s)]` and `SomeTypeList[Trait(s)]` syntax. These forms support both a single trait and trait compositions, and let you express conformance where you use the type instead of declaring a parameter in one place and using it in another. For example: ```mojo def my_parameterized_fn[T: Trait(s)](value: T): ``` becomes: ```mojo def my_parameterized_fn(value: Some[Trait(s)]): ``` You can use `Some` with any trait or trait composition, under any argument convention, wherever you've been using type parameters. If the compiler can't infer a concrete type, it errors and asks you to use explicit type parameters instead. ### Arguments `Some` places the trait requirement directly on the argument, eliminating the explicit type parameter: ```mojo # Before def foo[T: Intable, //](x: T) -> Int: return x.__int__() # After def foo(x: Some[Intable]) -> Int: return x.__int__() ``` ### Function types `Some` works with function types too, moving closure parameter conformance onto the argument: ```mojo # Before def sync_parallelize[ FuncType: def(Int) -> None, ](func: FuncType): ... # After def sync_parallelize(func: Some[def(Int) -> None]): ... ``` ### Variadics Use `*SomeTypeList` to conform a variadic parameter pack instead of a single type parameter: ```mojo # Before def show[*Ts: Writable](*pack: *Ts): ... # After def show(*pack: *SomeTypeList[Writable]): ... ``` ### Operator overloads Operator overloads are a natural fit for `Some`, the trait that enables the operator lives directly on the declaration: ```mojo # Before def __getitem__[I: Indexer, //](self, idx: I) -> ref[self.x] Self.T: ... # After def __getitem__(self, idx: Some[Indexer]) -> ref[self.x] Self.T: ... ``` ### Where `Some` won't work `Some` can't replace type parameters in all cases. The compiler can't infer a concrete type for struct fields, and will report an error indicating the type isn't concrete and asking you to use `[]` to bind missing parameters: ```mojo @fieldwise_init struct Struct(Writable): # Error: a `Some` struct field has no concrete type to infer var x: Some[Copyable & Deinitable & Writable] def main(): var s = Struct(1) print(s) ``` Move the conformances back to a parameterized type parameter to fix this: ```mojo @fieldwise_init struct Struct[T: Copyable & Deinitable & Writable](Writable): var x: Self.T def main(): var s = Struct(1) print(s) # Struct[Int](x=1) ``` ## Using conditional availability {#downcasting-safely} Sometimes a conformance is broader than you need. For example, a parameterized type may be constrained to `AnyType` in a struct, but you want to use it as `Writable` in a given method. Add a `where` clause to require `Writable`, allowing the compiler to prove the conformance before allowing a concrete instance to call the method: ```mojo def process(self, value: Self.T) where conforms_to(Self.T, Writable): print(value) ``` Call `conforms_to(T, Trait)` to test whether `T` satisfies a trait. Combine multiple checks with `and` or `or` to express more complex conditions. Conditions are not limited to trait checks. They can also constrain compile-time facts, such as a data length being a power of two or a capacity being positive. Any compile-time expression with a known value can appear in a `where` clause. The compiler rejects calls that don't satisfy the constraint. For example, a type built with a non-writable type `T` can't call `process()`: ```mojo self.process(non_writable) # Compile-time error, constraint violation ``` `where` clauses let the compiler prove conditions before constructing types, resolving conformances, compiling methods and functions, or manifesting compile-time declarations. Beyond conformance, a where clause can test predicates on its parameters: whether a `DType` is floating point or numeric (`dtype.is_floating_point()`, `dtype.is_numeric()`), or whether an integer parameter is a power of two. What it can't do is evaluate target facts like `is_64bit()` or `is_nvidia_gpu()`, or arbitrary compile-time functions, as constraints. The compiler can't carry those as proof. For those cases, use `comptime if` instead, which evaluates its condition directly. ## Parameterized values {#value-generics} Values parameterize code over compile-time constants instead of types. You declare them in `[]` alongside type parameters, but they bind to values. ### When to use parameterized values {#when-to-use-value-generics} Use parameterized values when a value shapes structure or behavior and is known at compile time. Common cases: - *Fixed sizes:* buffer lengths, array dimensions, matrix shapes - *Thresholds and limits:* capacity caps, retry counts, precision levels - *Feature selection:* algorithm variants, debug flags, mode switches - *Numeric configuration:* SIMD widths, stride lengths, unroll factors The compiler specializes code for each distinct value. It can remove dead branches (`comptime if`), unroll loops (`comptime for`), replace inline constants, and optimize aggressively with no runtime cost. ### Basic example This function creates a fixed-size list initialized with a default value: ```mojo comptime MyCollectionElement = ImplicitlyCopyable & Deinitable def make_filled[T: MyCollectionElement, size: Int]( splat_value: T ) -> List[T]: var result = List[T](capacity=size) for _ in range(size): result.append(splat_value) return result^ ``` The `size` parameter resolves at compile time. Each call site with a different value gets its own specialized version: ```mojo var three_zeros = make_filled[Int, 3](0) var five_hellos = make_filled[String, 5]("hello") print(three_zeros) # [0, 0, 0] print(five_hellos) # [hello, hello, hello, hello, hello] ``` ### Value parameters vs runtime arguments Put values known at compile time in `[]`. Put values only known at runtime in `()`. Ask yourself: does the caller know this value when writing the code? If yes, use a parameter. If it depends on input, files, or runtime state, use an argument. ```mojo # size is compile-time: the compiler specializes def fixed[size: Int](): var buf = Array[Int, size](fill=0) # size is runtime: no specialization def dynamic(size: Int): var buf = List[Int](capacity=size) ``` ## Parameterized types and explicit destruction {#generics-and-explicit-destruction} Explicitly destroyed types don't always work with parameterized code. The issue isn't parameterization; it's lifetime management. Explicit destruction gives you control over teardown: you can define deinitializers that take arguments, follow different paths, or raise errors. Parameterized code that copies or moves values can't see or honor that logic. Once a value is copied or transferred, you lose control over how (or whether) it's cleaned up, and the code won't compile. Watch for these cases: - Parameterized code that manages lifetimes - Parameterized code that copies or transfers values These show up most often in containers, collections, and iterators that copy values or take ownership and decide when destruction happens. Safe cases are parameterized operations that don't affect lifetimes, such as comparisons and predicates. If your parameterized code needs to own, copy, or control when a value dies, avoid explicitly destroyed types. Add a `Deinitable` constraint to keep things working. ## Conditional trait conformance :::caution Certain capabilities may be unstable or unusable during roll-out. Use caution with `RegisterPassable` and `TrivialRegisterPassable`. ::: *Conditional trait conformance* uses checks before allowing a type to adopt a trait. When the condition is satisfied, the type conforms. It must fulfill the trait's requirements and gains any default implementation the trait provides. When the condition isn't satisfied, Mojo skips the conformance. ### Example: derived conformance In the following declaration, Mojo conforms `Wrapper` to `Writable` when its parameter `T` is also `Writable`: ```mojo comptime BaseTraits = Copyable & Deinitable & Writable @fieldwise_init struct Wrapper[T: BaseTraits]( Writable where conforms_to(T, Writable) ): var value: Self.T ``` When conforming to `Writable`, `Wrapper` doesn't need to implement any methods. The trait provides a default implementation of `write_to()`. Now consider a type that isn't `Writable`: ```mojo @fieldwise_init struct NotWritable(BaseTraits): var data: Int ``` When instantiated with `Int` or `String` (both `Writable`), `Wrapper` gains `Writable` conformance. With `NotWritable`, you can build the struct, but you can't print it: ```mojo var w_int = Wrapper[Int](42) # Int is Writable print(w_int) # Wrapper[Int](value=42) var w_str = Wrapper[String]("Hello") # String is Writable print(w_str) # Wrapper[String](value=Hello) # OK: only `Writable` conformance is unavailable var w_not_writable = Wrapper[NotWritable](NotWritable(10)) # print(w_not_writable) # Compile-time error: # Wrapper[NotWritable] doesn't conform to Writable ``` This pattern is standard for single-type containers like `Optional[T]`, `Box[T]`, `Lazy[T]`, and `List[T]`. It says: "This type can do X if its inner type can do X." ### Example: parts conformance For types with multiple distinct components, the pattern extends naturally. `Result[T, E]`, `Pair[L, R]`, `Dict[K, V]`, and similar types say: "This type can do X if each of its parts can do X": ```mojo comptime BaseTraits = Copyable & Deinitable @fieldwise_init struct Pair[L: BaseTraits, R: BaseTraits]( Hashable where conforms_to(L, Hashable) and conforms_to(R, Hashable) ): var left: Self.L var right: Self.R @fieldwise_init struct NotHashable(BaseTraits): var data: Int ``` When both `L` and `R` are `Hashable`, the concrete `Pair` type becomes `Hashable`, gaining the `hash()` method from the trait: ```mojo var pair = Pair[Int, String](left=1, right="one") var hash = hash(pair) print(hash) # Prints the hash of the pair # OK: only hashing is unavailable var pair2 = Pair[Int, NotHashable](left=1, right=NotHashable(10)) # var hash2 = hash(pair2) # Compile-time error ``` ### Example: conditional method access When a type adopts a trait, it must satisfy the trait's required methods. You gate those method implementations with `where` clauses, the same conditions that control whether the conformance applies. You can make `Wrapper` testable in `if` statements by conforming to `Boolable`, which requires `__bool__()`: ```mojo @fieldwise_init struct Wrapper[T: BaseTraits]( Writable where conforms_to(T, Writable), Boolable where conforms_to(T, Boolable), ): var value: Self.T def __bool__(self) -> Bool where conforms_to(Self.T, Boolable): return self.value.__bool__() ``` The condition on `__bool__()` matches the one used for the `Boolable` conditional conformance. Since the method and the conformance use the same condition, they stay aligned. You won't end up with a conformance but no method, or a method without the corresponding conformance. ```mojo var w_str = Wrapper[String]("Hello") if w_str: # Chooses the non-empty branch print(t"Non-empty string \"{w_str.value}\" is truthy") else: print(t"Empty string \"{w_str.value}\" is falsy") var w_empty_str = Wrapper[String]("") if w_empty_str: # Chooses the empty branch print(t"Non-empty string \"{w_empty_str.value}\" is truthy") else: print(t"Empty string \"{w_empty_str.value}\" is falsy") ``` Because `NotWritable` isn't `Boolable`, the condition on `__bool__()` fails and the method isn't available: ```mojo @fieldwise_init struct NotWritable(BaseTraits): var data: Int var w_not_writable = Wrapper[NotWritable](NotWritable(10)) # Compile-time error: the method condition is false if w_not_writable: print(t"NotWritable with data {w_not_writable.value.data} is truthy") else: print(t"NotWritable with data {w_not_writable.value.data} is falsy") ``` `where` clauses on methods are useful beyond trait conformance too. You can gate a method so it only works with non-empty lists, real numbers, or values that fall within a collection's index bounds. ### Conditional trait composition Mojo supports flexible condition composition: - **Unconditional** — no special clauses: ```mojo struct Foo(Copyable, Deinitable): ``` - **Simple condition** — as shown in `Wrapper`: ```mojo struct Wrapper[T: BaseTraits]( Writable where conforms_to(T, Writable) ): ``` - **Hybrid** — mixes unconditional and conditional traits: ```mojo struct Foo[T: AnyType]( Copyable, Writable where conforms_to(T, Writable) ) ``` - **Multiple aligned conditions** — as shown in `Pair`: ```mojo struct Pair[L: BaseTraits, R: BaseTraits]( Hashable where conforms_to(L, Hashable) and conforms_to(R, Hashable) ): ``` - **Multiple independent conditions**: ```mojo struct Foo[T: AnyType]( Writable where conforms_to(T, Writable), Hashable where conforms_to(T, Hashable), ) ``` ### Conditional conformance with value parameters Conditional conformance also works with value parameters. You can gate both conformance and methods on compile-time value conditions. This is useful when a type's capabilities depend on a numeric parameter. For example, a fixed-capacity wrapper might only be `Writable` when it has capacity and its elements are writable. The `Sized` conformance is unconditional: ```mojo comptime ElementTraits = Writable & Copyable & Deinitable struct SizedListWrapper[capacity: Int, T: ElementTraits]( Sized, Writable where conforms_to(T, Writable) and capacity > 0 ): var data: List[Self.T] def __init__(out self, value: Self.T): self.data = List[Self.T](capacity=Self.capacity) for _ in range(Self.capacity): self.data.append(value.copy()) def __len__(self) -> Int: return len(self.data) def write_to(self, mut writer: Some[Writer]): writer.write(repr(self.data)) ``` You can gate methods on value conditions: ```mojo def first(self) -> Self.T where Self.capacity > 0: return self.data[0].copy() ``` When `capacity` is one or more, the type conforms to `Writable` and `first()` is available. When it's zero or less, neither is usable: ```mojo var s = SizedListWrapper[5, Int](42) print(s) # List of 42s print(s.first()) # 42 # var s = SizedListWrapper[0, Int](42) # print(s) # Error: Writable not satisfied # print(s.first()) # Error: constraint is false ``` Value conditions follow the same rules as type conditions. You can combine them with `and` and mix them with `conforms_to` checks in the same `where` clause. --- ## Get started with Mojo Get started with Mojo by building [Conway's Game of Life](https://en.wikipedia.org/wiki/Conway%27s_Game_of_Life), a simulation in which cells live, die, and reproduce based on the state of their neighbors. This tutorial walks you through the steps to build a simple version of the game. It should take you about 45-60 minutes to complete. Don't feel you need to rush it. Whether you arrive from, say, C++ or Python, you'll encounter unfamiliar syntax like transfer operators and compile-time variables. Read the *Takeaways* items to map these new features to concepts you already know. Run the code in your terminal and watch the grid evolve over time. As you build the game, you'll learn the Mojo syntax you need to start writing programs of your own. :::tip Before you start Make sure you've [installed Mojo](/install) and can build and run Mojo code. If you're using an AI coding assistant, install [Mojo agent skills](/docs/tools/skills). The skills track current Mojo syntax and language features. ```bash npx skills add modular/skills ``` ::: ## Game state Conway's Game of Life runs on a two-dimensional grid. Each cell is either alive or inactive. You'll use `1` for live cells and `0` for inactive cells. Count the neighbors around each cell by adding their values to a running sum. Create `life.mojo`. Build an 8 x 8 grid containing a [glider](https://en.wikipedia.org/wiki/Glider_(Conway%27s_Game_of_Life)): ```mojo def main(): var count: Int = 64 var num_cols: Int = 8 var glider_grid = List[Int](length=count, fill=0) # Convert (x, y) coordinates to a linear index in the grid var to_index = lambda (x: Int, y: Int) -> Int: y * num_cols + x # Set up the grid for coord in [(0, 1), (1, 2), (2, 0), (2, 1), (2, 2)]: glider_grid[to_index(coord[0], coord[1])] = 1 # Print the grid for index in range(count): print("X" if glider_grid[index] else ".", end="") if index % num_cols == (num_cols - 1): print() ``` Run the program to see the initial glider configuration: ```bash mojo life.mojo ``` Output: ```output ..X..... X.X..... .XX..... ........ ........ ........ ........ ........ ``` ### Takeaways - All variable declarations like `count` and `num_cols` start with `var`. - `var` bindings are mutable by default. - The `to_index` *lambda expression* defines a small local function that converts coordinates to a list index. Lambdas are short anonymous functions that evaluate a single expression. :::tip Worth knowing When assigning contents, a list expression sets the values: ```mojo var values: List[Int] = [12, -7, 64] # This is a list expression ``` In type names, square brackets supply compile-time *parameters*: ```mojo List[Int] # List is a standard library-supplied type Grid[8, 8] # Grid is a custom type ``` Parentheses supply run-time *arguments*: ```mojo print(value) Grid[8, 8]() ``` ::: ## Add reusable printing Move the display loop to a reusable function. Place this above `main()`: ```mojo def print_grid(grid: List[Int], num_cols: Int): for index in range(len(grid)): print("X" if grid[index] else ".", end="") if index % num_cols == (num_cols - 1): print() ``` Replace the print loop with `print_grid(glider_grid, num_cols)` and run. Improve performance by replacing individual prints with a single string. This solution uses *comprehensions*: ```mojo def print_grid(self): var grid_str = "".join( [(("X" if value else ".") + ("\n" if (index + 1) % Self.num_cols == 0 else "")) for index, value in enumerate(self.cells) ] ) print(grid_str) ``` When running the program with larger grids and many generations, this approach is faster than printing each cell individually. You print once per frame instead of once per cell. Call `print_grid()` from `main()` and confirm the output: ```mojo # Print the grid print_grid(glider_grid, num_cols) ``` ### Takeaways - Comprehensions are a shorthand for creating lists and other collections.`print_grid()` uses a list comprehension to transform each cell into a `String`, then joins them (`join()`) for printing. - Enumeration (`enumerate()`) accesses each cell's index and value, selecting "X" for live cells or "." for inactive cells. It also adds newlines between rows. ## Add lookups Make a few more changes in place in `life.mojo`: ```mojo comptime count: Int = 64 comptime num_cols: Int = 8 # Transform coordinates to a linear index comptime to_index = lambda (x: Int, y: Int) -> Int: ( y * num_cols + x ) # Transform linear index to coordinates comptime to_coord = lambda (i: Int) -> Tuple[Int, Int]: ( (i % num_cols, i // num_cols) # `//` is flooring divide ) def print_grid(grid: List[Int]): for index in range(len(grid)): print("X" if grid[index] else ".", end="") if index % num_cols == (num_cols - 1): print() def main(): var glider_grid: List[Int] = List[Int](length=count, fill=0) # Set up the grid for coord in [(0, 1), (1, 2), (2, 0), (2, 1), (2, 2)]: glider_grid[to_index(coord[0], coord[1])] = 1 # Print the grid print_grid(glider_grid) ``` Now you can convert coordinates to indices and indices to coordinates. ### Takeaways - `comptime` declarations are evaluated at compile time before the code runs. This lets Mojo optimize and generate efficient machine code. - The flooring divide operator (`//`) used here performs integer division, rounding towards negative infinity. For integer types, `/` returns an integer, rounding towards zero. - You've moved all the constants out from `main()` to `comptime` declarations, making them available throughout this file. - You've created two lambda expressions that convert between coordinates and indices. - You've removed the second argument from `print_grid()`. ### Try this In a separate source file, declare the number of rows as a comptime constant and compute the *count* at compile time. Remove the new source after verifying your solution is correct. You will always need two of these three items: the number of rows, the number of columns, and the total count of cells. ## Define a `Grid` type Revise your code again, into a new file called `grid.mojo`. You're creating a new type, called a `struct`: ```mojo struct Grid[num_cols: Int, num_rows: Int]: var cells: List[Int] var count: Int def __init__(out self): self.count = Self.num_cols * Self.num_rows self.cells = List[Int](length=self.count, fill=0) def main(): var glider_grid = Grid[8, 8]() ``` `__init__(out self)` initializes a new `Grid`. Every field must receive a value before the initializer returns. Here, `count` comes from the grid dimensions, and `cells` starts as a list of zeros. ### Takeaways - `Grid` uses both compile-time parameters and runtime fields. - `Self.num_cols` and `Self.num_rows` belong to the parameterized type. - `self.cells` and `self.count` belong to one `Grid` instance. - This `Grid` needs custom initialization, so it defines `__init__()`. :::tip Worth knowing Normally, if an initializer just assigns arguments directly to fields, add `@fieldwise_init` to the struct instead. Mojo generates that initializer for you. For example, in an alternate implementation you might define all three core measurements as fields in your struct rather than synthesize one from the other two: ```mojo @fieldwise_init struct Grid: var cells: List[Int] var count: Int var num_cols: Int var num_rows: Int ``` ::: ## Add grid operations Put the following content into your `Grid` struct: ```mojo comptime to_index = lambda (x: Int, y: Int) -> Int: ( y * Self.num_cols + x ) comptime to_coord = lambda (i: Int) -> Tuple[Int, Int]: ( (i % Self.num_cols, i // Self.num_cols) ) def print_grid(self): var grid_str = "" for index in range(len(self.cells)): grid_str += "X" if self.cells[index] else "." if (index % Self.num_cols == (Self.num_cols - 1) and index != len(self.cells) - 1): grid_str += "\n" print(grid_str) def __setitem__(mut self, coord: Tuple[Int, Int], value: Int): self.cells[Self.to_index(coord[0], coord[1])] = value def __getitem__(self, coord: Tuple[Int, Int]) -> Int: return self.cells[Self.to_index(coord[0], coord[1])] ``` ### Takeaways - `Self.to_index()` accesses a type member. The lambda `to_index()` belongs to `Grid`. Uppercase `Self` refers to the type. - Instance methods take `self` as their first argument. Lowercase `self` refers to an instance. - `mut self` allows a method to change the instance. - `__getitem__()` and `__setitem__()` define indexing behavior, so a `Grid` can use `grid[coord]` notation. Don't add this anywhere. It's just a preview of how this all works from the call site: ```mojo # Set up the grid for coord in [(0, 1), (1, 2), (2, 0), (2, 1), (2, 2)]: glider_grid[coord] = 1 # Uses indexing with `__setitem__()` ``` ## Import `Grid` Remove `main()` from `grid.mojo`, save it, and import your new type into `life.mojo`: ```mojo from grid import Grid # Separate concerns into separate files def main(): var glider_grid = Grid[8, 8]() # Set up the grid for coord in [(0, 1), (1, 2), (2, 0), (2, 1), (2, 2)]: glider_grid[coord] = 1 # Print the grid glider_grid.print_grid() ``` Run it and confirm everything works as expected. ## Be random Gliders are terrific for validating code, but random values produce great animations. Set up your seed in `main()`: ```mojo from std.random import seed from grid import Grid def main(): seed() # ... ``` Mojo won't allow runtime statements at global scope, so you can't call `seed()` there. Instead, seed the RNG from executable code, such as `main()` or another function. You only need to seed once: seeding sets the state of a single PRNG shared across threads. ### Add protection Filling every cell independently gives you random static. It works, but it doesn't produce especially interesting Game of Life patterns. Instead, build a few random clumps of live cells. To start, add checks to your setter in `grid.mojo`. A coordinate check function helps: ```mojo def is_valid_coord(self, coord: Tuple[Int, Int]) -> Bool: return not ( coord[0] < 0 or coord[0] >= Self.num_cols or coord[1] < 0 or coord[1] >= Self.num_rows ) def __setitem__(mut self, coord: Tuple[Int, Int], value: Int): if not self.is_valid_coord(coord): return # no op self.cells[Self.to_index(coord[0], coord[1])] = value ``` You can add a check to the getter, too. Instead of returning a made-up value, raise an error or abort the process. The best approach tests the coordinates before indexing (`if self.is_valid_coord(coord):`) to avoid indexing errors instead of adding no-op workarounds. ### Introducing errors This version of `__getitem__()` raises an error when coordinates aren't valid. Add `raises` to the signature before the arrow, and call `raise`: ```mojo def __getitem__(self, coord: Tuple[Int, Int]) raises -> Int: if not self.is_valid_coord(coord): raise String(t"Invalid coordinate: ({coord[0]}, {coord[1]})") return self.cells[Self.to_index(coord[0], coord[1])] ``` A *TString* starts with `t"` and creates a template format. `print()` statements automatically convert to strings, but everywhere else explicitly call `String()`. Once `__getitem__()` raises, every caller must either raise or handle the error using Mojo's `try/except` error handling. For now, revert your changes and let `__getitem__()` handle invalid coordinates as it did before. ### Construct the random grid Add a static method that constructs a random `Grid`: ```mojo from std.random import random_si64 # Add this import to grid.mojo # and inside Grid: @staticmethod def random_grid(clumps: Int = 2) -> Self: var grid = Self() for _ in range(clumps): var idx = Int(random_si64(0, Int64(grid.count) - 1)) var x, y = Self.to_coord(idx) grid[(x, y)] = 1 # Fill index cell for dx in range(-1, 2): # -1, 0, or 1 for dy in range(-1, 2): if not grid.is_valid_coord((x + dx, y + dy)): continue if random_si64(0, 3) > 0: continue # 75% skip grid[(x + dx, y + dy)] = 1 return grid^ ``` By default, `random_grid()` builds two clumps when called without arguments (`clumps: Int = 2`). The default value follows the equal sign. A `@staticmethod` belongs to the type and not an individual instance. Call it through the `Grid` type: ```mojo print("\nRandom grid:\n") var random_grid = Grid[8, 8].random_grid() random_grid.print_grid() ``` You don't have to add this code except to test it. You can remove it afterwards. ### Takeaways - This static method still needs `Grid` parameters, namely 8 and 8. - Each neighboring cell has a 25% chance of becoming live. Adjust to your preference. - `random_si64()` returns an `Int64`. Cast the value to `Int`. - The clump loop variable is `_`, the discard pattern. Use this when you don't care about the value it produces. - A `^` sigil transfers the newly initialized `Grid`, avoiding a copy. Transfer means changing ownership, handing the value to the new owner. By skipping a copy, you avoid the time and memory costs of duplicating data. ## Evolve In `life.mojo`, cut out the glider grid and remove the print statements. Next, you'll start making the grid change over time. Start by adding a separate `next_cells` list to `Grid` and initialize it: ```mojo struct Grid[num_cols: Int, num_rows: Int]: var cells: List[Int] # Holds the current generation var next_cells: List[Int] # Holds the next generation var count: Int def __init__(out self): self.count = Self.num_cols * Self.num_rows self.cells = List[Int](length=self.count, fill=0) self.next_cells = List[Int](length=self.count, fill=0) ``` Conway's Game of Life applies three rules to every cell: - A live cell stays alive with two or three live neighbors. - An inactive cell becomes alive with exactly three live neighbors. - Every other cell is inactive in the next generation. The updated cells are stored in the `next_cells` list. They won't affect math for the previous generation. Each cell has eight neighbors: the cells in the 3 x 3 square around it, excluding the cell itself. Add these methods to `Grid`. `evolve_cell()` determines the state for the next generation of a single cell based on its neighbors: ```mojo def evolve_cell(mut self, i: Int): var is_live = Bool(self.cells[i]) self.next_cells[i] = 0 # Count the neighbors var ncount = -1 if is_live else 0 # Exclude self from the count var x, y = Self.to_coord(i) for dx in range(-1, 2): for dy in range(-1, 2): var nx = x + dx var ny = y + dy ncount += self.cells[Self.to_index(nx, ny)] # Live cell stays alive with two or three live neighbors if is_live and (ncount == 2 or ncount == 3): self.next_cells[i] = 1 # Inactive cell becomes alive with exactly three live neighbors elif not is_live and ncount == 3: self.next_cells[i] = 1 ``` `evolve()` updates the entire grid to the next generation by calling `evolve_cell()` for each index: ```mojo def evolve(mut self): for i in range(self.count): var x, y = Self.to_coord(i) # Edges are excluded from evolution and will always go inactive. if (x == 0 or y == 0 or x == Self.num_cols - 1 or y == Self.num_rows - 1): self.next_cells[i] = 0 # Edges go inactive continue self.evolve_cell(i) # Swap the current and next cell states var tmp = self.cells^ # Transfer self.cells = self.next_cells^ # Transfer self.next_cells = tmp^ # Every cell is written, so this is safe ``` ### Takeaways - The algorithm progresses in integer order, excluding edges. - Live cells offset by -1, to exclude them from the count. - Mojo's "ternary" has no `? :` syntax. Use the Python-style `if-else` expression instead: `-1 if is_live else 0`. ## Run the simulation Add a loop so you can watch the grid evolve in the terminal. Here's the final `life.mojo`: ```mojo from std.random import seed from grid import Grid from std.time import sleep def main(): comptime gridw: Int = 80 comptime gridh: Int = 20 comptime grid_count: Int = 400 seed() var grid = Grid[gridw, gridh].random_grid(grid_count) while True: for gen in range(100): print(t"\033[H\033[J\nGeneration: {gen}{' ' * 4}") grid.evolve(); grid.print_grid() sleep(0.1) grid = Grid[gridw, gridh].random_grid(grid_count) ``` ### Takeaways - `comptime` declarations let you set constants. - The odd characters in the print statement are ANSI escape codes. You'll see the updates generation-by-generation. Keep your terminal at a minimum of 80x24 for best results. :::tip Worth knowing Mojo uses semicolons to separate statements, not to end them. One statement per line is the usual form. This listing pairs them to stay compact. ::: - Mojo uses semicolons to separate statements, not to end them. One statement per line is the usual form. This listing pairs them to stay compact. ## Your first day? Try these - To use AI coding assistants with Mojo, see [our AI skills guide](/docs/tools/skills/) for using the latest up-to-date language know-how. - Our Mojo [language reference](/docs/reference/) section provides a concise reference for syntax, keywords, and more. - You can download our [cheat sheets](/docs/reference/cheat-sheets/) for printable reference cards that unify entire concepts. - [Mojo Quest](https://quest.mojolang.org/) is a web-based game where you solve coding challenges to practice Mojo syntax. ## Final code
View the complete grid.mojo ```mojo from std.random import random_si64 struct Grid[num_cols: Int, num_rows: Int]: var cells: List[Int] # Holds the current generation var next_cells: List[Int] # Holds the next generation var count: Int def __init__(out self): self.count = Self.num_cols * Self.num_rows self.cells = List[Int](length=self.count, fill=0) self.next_cells = List[Int](length=self.count, fill=0) comptime to_index = lambda (x: Int, y: Int) -> Int: ( y * Self.num_cols + x ) comptime to_coord = lambda (i: Int) -> Tuple[Int, Int]: ( (i % Self.num_cols, i // Self.num_cols) ) def print_grid(self): var grid_str = "".join( [(("X" if value else ".") + ("\n" if (index + 1) % Self.num_cols == 0 else "")) for index, value in enumerate(self.cells) ] ) print(grid_str) def is_valid_coord(self, coord: Tuple[Int, Int]) -> Bool: return not ( coord[0] < 0 or coord[0] >= Self.num_cols or coord[1] < 0 or coord[1] >= Self.num_rows ) def __setitem__(mut self, coord: Tuple[Int, Int], value: Int): if not self.is_valid_coord(coord): return # no op self.cells[Self.to_index(coord[0], coord[1])] = value def __getitem__(self, coord: Tuple[Int, Int]) -> Int: return self.cells[Self.to_index(coord[0], coord[1])] @staticmethod def random_grid(clumps: Int = 2) -> Self: var grid = Self() for _ in range(clumps): var idx = Int(random_si64(0, Int64(grid.count) - 1)) var x, y = Self.to_coord(idx) grid[(x, y)] = 1 # Fill index cell for dx in range(-1, 2): # -1, 0, or 1 for dy in range(-1, 2): if not grid.is_valid_coord((x + dx, y + dy)): continue if random_si64(0, 3) > 0: continue # 75% skip grid[(x + dx, y + dy)] = 1 return grid^ def evolve_cell(mut self, i: Int): var is_live = Bool(self.cells[i]) self.next_cells[i] = 0 # Count the neighbors var ncount = -1 if is_live else 0 # Exclude self from the count var x, y = Self.to_coord(i) for dx in range(-1, 2): for dy in range(-1, 2): var nx = x + dx var ny = y + dy ncount += self.cells[Self.to_index(nx, ny)] # Live cell stays alive with two or three live neighbors if is_live and (ncount == 2 or ncount == 3): self.next_cells[i] = 1 # Inactive cell becomes alive with exactly three live neighbors elif not is_live and ncount == 3: self.next_cells[i] = 1 def evolve(mut self): for i in range(self.count): var x, y = Self.to_coord(i) # Edges are excluded from evolution and will always go inactive. if ( x == 0 or y == 0 or x == Self.num_cols - 1 or y == Self.num_rows - 1 ): self.next_cells[i] = 0 # Edges go inactive continue self.evolve_cell(i) # Swap the current and next cell states var tmp = self.cells^ # Transfer self.cells = self.next_cells^ # Transfer self.next_cells = tmp^ # Every cell is written, so this is safe ```
View the complete life.mojo ```mojo from std.random import seed from grid import Grid from std.time import sleep def main(): comptime gridw: Int = 80 comptime gridh: Int = 20 comptime grid_count: Int = 400 seed() var grid = Grid[gridw, gridh].random_grid(grid_count) while True: for gen in range(100): print(t"\033[H\033[J\nGeneration: {gen}{' ' * 4}") grid.evolve() grid.print_grid() sleep(0.1) grid = Grid[gridw, gridh].random_grid(grid_count) ```
--- ## Mojo Manual Welcome to the Mojo Manual, the authoritative learning path for Mojo. This manual is for developers and researchers who know how to program and want to build with Mojo. The Mojo Manual gets you working quickly, then goes deeper into the ideas that give Mojo its range. You'll move from core Mojo into its programming model, compile-time features, and direct control of values, memory, and hardware. Ready to write some code? - [Quickstart](/docs/manual/quickstart/) gives you a fast tour of Mojo syntax and other fundamentals while confirming that your toolchain is set up and working. - [Build Conway's Game of Life](/docs/manual/get-started) for a bigger project that walks you through creating a complete Mojo command-line application. As you read, keep these references handy: - [The Mojo language reference](/docs/reference/) provides quick lookups for syntax, keywords, and more. - [Cheat sheets](/docs/reference/cheat-sheets/) bring entire concepts together in printable, visual reference cards. --- ## Automatic destruction Mojo destroys values as soon as they're no longer used. It doesn't wait for the end of a code block or even the end of an expression. With Mojo's *as-soon-as-possible* (ASAP) destruction policy, intermediate values in an expression such as `a + (b - c) * d` can be destroyed as soon as their last use completes. At compile time, Mojo determines the last use of each value. After its last use, the value's lifetime ends and Mojo calls its `__deinit__()` deinitializer. For cleanup that must happen at a specific, compiler-checked point, Mojo also supports *explicit* deinitializers. See [Explicit value destruction](/docs/manual/lifecycle/explicit-destroy/). ## When Mojo destroys values Track when each Number instance is deinitialized by overloading `__deinit__()` to print a message: ```mojo @fieldwise_init struct Number(Writable): var value: Int # Track the destruction of each Number instance def __deinit__(deinit self): print(t"Destroying Number(value={self.value})") # Add two Number values together def __add__(self, other: Number) -> Number: return Number(self.value + other.value) # Subtract a Number value from another def __sub__(self, other: Number) -> Number: return Number(self.value - other.value) # Multiply two Number values together def __mul__(self, other: Number) -> Number: return Number(self.value * other.value) def main(): var a = Number(1) var b = Number(2) var c = Number(3) var d = Number(4) # 1 2 3 4 print(a + (b - c) * d) # The output shows the order of destruction for each value. # Expression precedence determines the order of evaluation, and # therefore the order in which these last uses occur: # Destroying Number(value=3) # PARENTHESIZED SUBTRACTION FIRST # Destroying Number(value=2) # PARENTHESIZED SUBTRACTION FIRST # Destroying Number(value=-1) # MULTIPLICATION SECOND, intermediate value # Destroying Number(value=4) # MULTIPLICATION SECOND # Destroying Number(value=-4) # ADDITION THIRD, intermediate value # Destroying Number(value=1) # ADDITION THIRD # Number(value=-3) # PRINTS RESULT # Destroying Number(value=-3) # RESULT IS DESTROYED; DEINITIALIZER RUNS ``` Every value is initialized once and deinitialized once. This happens as the expression is evaluated in precedence order: the parenthesized subtraction first, then multiplication, then addition. Values are destroyed as soon as their last use completes. Intermediate values that aren't bound to variables (-1, -4, and -3 in this example) also have lifecycles. They're destroyed after their last use. ## Deinitializer behavior `__deinit__()` uses the `deinit` [argument convention](/docs/manual/values/ownership/#argument-conventions) for `self`. The value and its fields remain valid while `__deinit__()` performs cleanup. When the method returns, the instance becomes logically deinitialized. Mojo generates a `__deinit__()` for every struct with deinitializable fields. In general, don't call `__deinit__()` directly. If you need that level of control, use [explicit value destruction](/docs/manual/lifecycle/explicit-destroy/). You may also need to call `__deinit__()` directly when wrapping a deinitializer for parameterized types. ## Moving values out of fields Deinitializers are the only place where you can safely move values out of an instance's fields without having to reinitialize the field before its next use. This special rule isn't about where the code lives. It's about guarantees: ```mojo def __deinit__(deinit self): var name = self.name^ # OK: can take ownership of fields ``` The compiler knows that deinitializers like `__deinit__()` are the final use of an instance. Because of this, it allows you to move values out of fields without reinitializing them. This ensures that fields are only moved out of struct instances when the compiler can guarantee the instance is at the end of its lifetime. Like instances, struct fields use ASAP destruction within `__deinit__()` methods. For example: ```mojo struct S: var a: String var b: String def __deinit__(deinit self): # Mojo calls a.__deinit__() here. use(b) # Mojo calls b.__deinit__() here. ``` ## Custom deinitializers Define a custom `__deinit__()` when your type needs to perform cleanup as it's destroyed. For example, you might free manually allocated memory or close a long-lived resource such as a file. This logger owns a temporary file. Its deinitializer closes the file when the logger is destroyed: ```mojo from std.tempfile import NamedTemporaryFile struct QuickLogger: var temporary_file: NamedTemporaryFile def __init__(out self) raises: self.temporary_file = NamedTemporaryFile(mode="w", delete=False) # Log a message to the temporary file def log(mut self, message: String) raises: self.temporary_file.write(message + "\n") def __deinit__(deinit self): try: print(t"Closing: {self.temporary_file.name}") self.temporary_file.close() except e: print(t"Error: {e}") def main() raises: var ql = QuickLogger() ql.log("This is a test log message.") ql.log("This is the last use of 'ql'") ``` ## Explicit lifetime extension Most of the time, Mojo's ASAP destruction requires no extra effort. You may need to explicitly mark the last use of a value to control when its deinitializer runs. Use explicit lifetime extension when something outside the value's ordinary uses still requires it to remain alive and you can't use origins or references to do this. Assign the value to the `_` *discard pattern* where you want its lifetime to end. This marks its last use, so the deinitializer runs immediately after the statement: ```mojo var s = "abc" print(s) # s.__deinit__() runs after this line # Extend t's lifetime to the discard line var t = "xyz" print(t) # ... some time later _ = t # t.__deinit__() runs after this line ``` Two cases particularly need explicit lifetime extension: an RAII guard, such as a lock, that would otherwise be released too early, and a pointer whose origin has been erased, which can lead to a use-after-free. Neither produces a compiler error. `_ = value` keeps the value alive until the point where you need it released or need the pointer to remain valid. :::note Previous versions of Mojo required the transfer sigil (`^`) when discarding a move-only type. This is no longer required, since the compiler doesn't move the discarded value. For more on the transfer sigil, see [ownership transfer](/docs/manual/values/ownership/#transfer-arguments-var-and-). ::: --- ## Explicit destruction Normally, Mojo automatically destroys a value after its last use. Explicit destruction lets a type opt out of that behavior and require its values to be deliberately destroyed. A type that does this is an explicitly destroyed type. Explicit destruction uses an unsatisfiable `Deinitable` constraint. Mark types with a `Deinitable where False` conditional conformance: ```mojo @fieldwise_init struct Example(Deinitable where (False, "call 'cleanup()'")): def cleanup(deinit self): # perform cleanup operations pass def main(): var value = Example() # use value value^.cleanup() ``` Provide named deinitializer methods that use the `deinit self` argument convention. Make the error message actionable and follow Mojo best practices: use lower case for `call`, put method and function names in single quotes, include the parentheses, and omit the final period. Values that require explicit destruction must be consumed by a named deinitializer or transferred out of scope. Otherwise, the compiler reports an error. ## When to use explicit destruction Choose explicit destruction when cleanup must be controlled in code. Common cases include: - Cleanup can fail and requires error handling. - Multiple cleanup paths are possible. - The order of cleanup operations matters. - Cleanup is expensive and should be deliberate. Examples: ```mojo # Multiple cleanup paths @fieldwise_init struct Transaction( Deinitable where (False, "call 'commit()' or 'rollback()'") ): def commit(deinit self) raises: # Offers error handling # ... pass def rollback(deinit self): # ... pass # Order matters struct MutexGuard(Deinitable where False): # Must be called to release the lock before other operations def unlock(deinit self): # ... pass ``` ### Raising deinitializers `__deinit__()` can't raise; the compiler rejects the `raises` keyword on it. A named deinitializer can raise, which lets cleanup report failure to its caller. Because `deinit self` consumes the value at the call, the binding is consumed whether the call returns or raises. The caller can't invoke another deinitializer on that same instance. If the type contains explicitly destroyed fields, the deinitializer must dispose of them before it raises. Implicitly destructible fields are cleaned up during unwinding. ## Custom error messages Add a custom message to the `where` constraint to improve compiler diagnostics: ```mojo struct CustomFileHandle( Deinitable where (False, "call 'save_and_close()' or 'discard()'") ): def save_and_close(deinit self) raises: pass def discard(deinit self): pass ``` ## Parameterized code and explicit destruction Parameterized code using broad constraints like `AnyType` and `Movable` can accept both implicitly and explicitly destroyed values. However, that code *can't* destroy values that require explicit destruction except under specific circumstances. These are the key patterns: ```mojo # Error if T requires explicit destruction def owning_function[T: AnyType](var value: T): pass # Error: value abandoned here # Constrain T to Deinitable when implicit destruction is acceptable def deinitable_function[T: Deinitable](var value: T): pass # value.__deinit__() called automatically # Return the value so the caller keeps destruction responsibility def pass_through_function[T: Movable](var value: T) -> T: return value^ # Accept a deinitializer so the function can destroy the value def consuming_function[ T: Movable, //, Cleanup: def(var T) ](var value: T, consume: Cleanup): consume(value^) # deinitialize here ``` ### Example types for parameterized destruction The following examples use two types with different destruction requirements: `Basic` is destroyed implicitly, while `Tally` requires explicit destruction. ```mojo from std.memory import Allocation, Layout, alloc, dealloc @fieldwise_init struct Basic(Movable): var string: String def __deinit__(deinit self): print(t"Destroying Basic: {self.string}") struct Tally(Movable, Deinitable where ( False, "call 'destroy()' to free the counters" )): var counts: Allocation[Int64] def __init__(out self, buckets: Int): self.counts = alloc(Layout[Int64](count=buckets)) self.counts.unsafe_span().fill(0) def record(mut self, bucket: Int): self.counts.unsafe_span()[bucket] += 1 def destroy(deinit self): print(t"Destroying Tally: {self.counts.unsafe_span()}") dealloc(self.counts^) ``` ### Parametric pass-through A parameterized pass-through function doesn't need to know its value's destruction model. The caller retains responsibility for ending the value's lifetime: ```mojo def pass_through[T: Movable](var value: T) -> T: # perform work with value return value^ ``` Since the function doesn't maintain value ownership, it doesn't need to know *how* to destroy it: ```mojo var tally = Tally(3) tally.record(2) tally = pass_through(tally^) tally^.destroy() # Destroying Tally: [0, 0, 1] var basic = Basic("Hello") _ = pass_through(basic^) # Destroying Basic: Hello ``` ### Parametric consumption: Movable Pass a deinitializer to a parameterized function so it knows how to consume any `Movable` value. The caller may transfer the value or a copy, knowing it won't be abandoned. ```mojo def consuming_method[ T: Movable, //, Cleanup: def(var T) ](var value: T, consume: Cleanup): print(t"Consuming: {reflect[T].name()}") consume(value^) ``` Each explicitly destroyed type has an unknown set of deinitializer methods. Parametric code can't anticipate what these are. Using a lambda helps you pass a type-specific deinitializer to a consuming parametric function. For example, a `Tally` value: ```mojo var tally = Tally(2) tally.record(1) comptime tally_consumer = lambda (var t: Tally): t^.destroy() consuming_method(tally^, tally_consumer) # Output: # Consuming: .Tally (consuming_method) # Destroying Tally: [0, 1] (destroy()) ``` ### Parametric consumption: Deinitable There's nothing to pass for all-Deinitable types that use no-argument deinitializers: ```mojo # Works across all Deinitable types comptime implicit_consumer = lambda [T: Deinitable]( var value: T ): T.__deinit__(value^) ``` For example, a `Basic` value: ```mojo var basic = Basic("World") consuming_method(basic^, implicit_consumer[Basic]) # Output: # Consuming: .Basic (from `consuming_method()`) # Destroying Basic: World (from `__deinit__()`) ``` ## Related - [Value destruction](/docs/manual/lifecycle/death/) - Complete coverage of value destruction and lifetime management - [`AnyType`](/docs/std/traits/anytype/AnyType/) - Base trait for all types - [`Deinitable`](/docs/std/traits/deinitable/Deinitable/) - Trait for automatically deinitializable types --- ## Value lifecycles A Mojo value has a beginning and an end. Mojo creates and initializes the value before you use it, then destroys it when it's no longer needed. Lifecycle methods define what happens at each stage. Use them to control how your types initialize their state, transfer values, and release resources. --- ## Initialization state Mojo tracks two kinds of initialization for structs: *fieldwise* and *logical*. Fieldwise initialization means every field contains a valid value. Logical initialization means the instance as a whole is valid and ready to use. A struct needs both before you can use it. ## The basics You create struct instances by calling the `__init__()` initializer: ```mojo struct Person: var name: String var age: Int def __init__(out self, name: String, age: Int): self.name = name self.age = age def main(): var me = Person("Alice", 30) ``` Calling `Person("Alice", 30)` is syntactic sugar for calling the initializer directly: ```mojo var me: Person me = Person.__init__("Alice", 30) # Identical ``` When constructing a Person, the compiler allocates the necessary storage and `__init__()` initializes that memory. ## Fieldwise vs logical initialization Initializing a struct by assigning values directly to its fields may populate the data, but it doesn't make the instance usable: ```mojo @fieldwise_init struct Person(Writable): var name: String var age: Int def main(): var me: Person me.name = "Alice" me.age = 25 print(me) # Error # error: 'me' used with all fields manually initialized # but without calling an '__init__' method ``` In this example, all fields contain valid values, but the instance is still not considered initialized. Assigning every field satisfies *fieldwise* initialization, but without running an `__init__()` method, it doesn't satisfy *logical* initialization. Construct the value with an initializer to establish both: ```mojo var me: Person # Not initialized me = Person("Alice", 30) # Logically and fieldwise initialized after call print(me) ``` After `__init__()` completes, the instance is safe to use. ## Inside `__init__()` Within `__init__()`, `self` is logically initialized, but its fields are uninitialized. This reverses the situation before calling `__init__()`, where the fields are initialized but `self` is not: ```mojo def __init__(out self, name: String, age: Int): # At this point: # - Logically initialized (self is valid as an instance) # - Fieldwise uninitialized (fields have no values yet) self.name = name self.age = age # Now both logically and fieldwise initialized ``` Entering `__init__()` establishes the instance. It's your responsibility to populate every field: ```mojo def __init__(out self, name: String, age: Int): self.name = name # Error: field 'age' not initialized in __init__ ``` The `__init__()` signature doesn't have to mirror the struct's fields. You can use parameters, constants, or external values to initialize them: ```mojo # Parameters can be used to initialize fields self._store = List[T](capacity=Count) # Constants can be used to initialize fields self.string = "" # External values can be used to initialize fields from std.math import pi self.default_angle = pi / 2.0 self.uuid = MyUUIDImplementation.uuid() ``` ### Calling methods You can't call methods until all fields are initialized: ```mojo def __init__(out self, name: String): self.greet() # Error: self not fully initialized self.name = name self.greet() # OK: all fields initialized ``` Field initialization is limited to `__init__()` methods. Regular methods can't initialize individual fields of an `out` argument, but `__init__()` methods can. --- ## Creating values A value's life in Mojo begins when you construct it, directly or implicitly. The type uses an *initializer* to prepare the value for use. Each constructible type provides one or more initializer overloads. Initializers set up the value's fields and perform any other required preparation. Initializers and deinitializers together define a value's lifecycle. This page covers value creation and initialization. ## Initializers {#constructor} For simple types, use `@fieldwise_init` to have Mojo generate an initializer: ```mojo @fieldwise_init struct MyStruct: var field1: Int var field2: String ``` For more complex types, or when you need more control, write your own initializer: ```mojo struct MyStruct: var field1: Int var field2: String def __init__(out self, field1: Int, field2: String): self.field1 = field1 self.field2 = field2 ``` An initializer must set up every field in a value. If any field is uninitialized when the initializer finishes, the compiler reports an error. All initializers use the `out self` argument convention. The initializer constructs `self` rather than declaring or explicitly returning a result: ```mojo # Works with both fieldwise initialization and # hand-written initializers var new_instance = MyStruct(1, "Hello") ``` Custom initializers can provide default values, calculate fields, validate arguments, or initialize resources. Where possible, Mojo automatically provides specialized initializers for types that conform to `Copyable` or `Movable`. ### Overloading initializers Like other methods, you can [overload](/docs/manual/functions/#overloaded-functions) `__init__()` to provide different ways to initialize a value. Initializer overloads can delegate to each other and use default arguments. For example: ```mojo struct RetryPolicy: var max_attempts: Int var delay_ms: Int # Factory-style convenience overload delegates to the core initializer def __init__(out self): self = Self(3) # Syntax sugar for `self.__init__(3)` # Core initializer provides the default delay def __init__(out self, max_attempts: Int, delay_ms: Int = 1000): self.max_attempts = max_attempts self.delay_ms = delay_ms ``` This provides several ways to construct the same type: ```mojo var standard = RetryPolicy() var persistent = RetryPolicy(10) var aggressive = RetryPolicy(10, 250) ``` ### Initializers and implicit conversion Mojo can implicitly convert values when a different type is required during assignment or when passing or returning a value. For example, `Optional[T]` supports implicit conversion from `T` and `None`: ```mojo var greeting: Optional[String] = None greeting = String("Salve!") ``` Enable implicit conversion by marking an initializer with [`@implicit`](/docs/reference/decorators/implicit/): ```mojo struct Target: @implicit def __init__(out self, source: Source): # ... ``` Use implicit conversions sparingly. They work best when the conversion is safe, constant-time, and has one clear meaning. For example: ```mojo struct Complex: var real: Float64 var imag: Float64 def __init__(out self, real: Float64, imag: Float64): self.real = real self.imag = imag @implicit def __init__(out self, value: Float64): self = Complex(value, 0.0) def magnitude_squared(value: Complex) -> Float64: return value.real * value.real + value.imag * value.imag def main(): # Implicitly converts 1.6 to Complex(1.6, 0.0) var complex: Complex = 1.6 # Implicitly converts 3.0 to Complex(3.0, 0.0) in call var result = magnitude_squared(3.0) ``` ### Initializer lists Without an `@implicit` initializer, `Complex` would lose its implicit conversion. *Initializer lists* provide another convenience: braced construction of an expected type without spelling its name. This syntax works whether the type provides implicit initialization or not: ```mojo # Instead of these full type construction calls: var result = magnitude_squared(Complex(real=3.0, imag=0.0)) # Full, keyword var result = magnitude_squared(Complex(3.0, 0.0)) # Full, positional var result = magnitude_squared(Complex(value=3.0)) # Convenience, keyword var result = magnitude_squared(Complex(3.0)) # Convenience, positional # With braced syntax: var result = magnitude_squared({real=3.0, imag=0.0}) # Full, keyword var result = magnitude_squared({3.0, 0.0}) # Full, positional var result = magnitude_squared({value=3.0}) # Convenience, keyword var result = magnitude_squared({3.0}) # Convenience, positional ``` This is useful for compiler-inferred parameterized types, whose full type names can be long and verbose. ### No-initializer types Mojo allows you to write types that can't be constructed. If a type declares no initializer, you can't create an instance and there's no lifecycle to manage. Use them to host static content and behavior without state: ```mojo struct HTTPStatus: comptime OK = 200 comptime NOT_FOUND = 404 comptime INTERNAL_SERVER_ERROR = 500 @staticmethod def is_success(code: Int) -> Bool: # 2xx is the HTTP status success class return 200 <= code < 300 ``` For example: ```mojo def handle(status_code: Int) -> String: if HTTPStatus.is_success(status_code): return "ok" return "failed" ``` ## Copy and move initializers Copy and move initializers use another value of the same type: ```mojo var the_copy = value.copy() # AKA ValueType(copy=value) var the_move = value^ # AKA ValueType(move=value^) ``` ### Copy initializer {#copy-constructor} `Copyable` establishes values that can be copied. It provides the `copy()` method and, when possible, Mojo synthesizes the required copy initializer: ```mojo def __init__(out self, *, copy: Self): # ... ``` A `Copyable` constraint lets generic code explicitly copy a value: ```mojo def copy_return[T: Copyable](foo: T) -> T: var copy = foo.copy() return copy^ ``` All `Copyable` types are also `Movable`, so you can transfer ownership of the copy when returning it, as shown here. ### Implicitly-copyable types `ImplicitlyCopyable` allows the compiler to insert copies where an explicit copy would otherwise be required. It refines `Copyable`, so conforming types also support `copy()` and the copy initializer. Use `ImplicitlyCopyable` only when implicit copying is required by the compiler or an API contract. Implicit copying can hide potentially expensive work. Prefer an explicit `copy()` call, especially when copying may allocate memory or otherwise have significant cost. This keeps the operation visible at the call site. ### Move initializer {#move-constructor} Consider the `RetryPolicy` type defined earlier on this page and the following example that transfers ownership of a policy value to a new variable: ```mojo var policy = RetryPolicy(3, 1000) var transferred = policy^ ``` Although `RetryPolicy` declared no conformances, the transfer operator still works here. Mojo synthesizes a move initializer for it. Define custom move initializers when transferring requires custom behavior: ```mojo def __init__(out self, *, deinit move: Self): # ... ``` ### Move-only and immovable types A type that conforms to `Movable` but not `Copyable` is move-only. For example, [`OwnedPointer`](/docs/std/memory/owned_pointer/OwnedPointer/) can transfer ownership of its stored value but can't copy it. [`Atomic`](/docs/std/atomic/atomic/Atomic/) is also move-only, preventing copies that would duplicate its value. A type conforming to neither `Movable` nor `Copyable` is immovable. To opt out of `Movable`, conform your type to `Movable where False` or define it with a non-movable field: ```mojo struct Pinned(Movable where False): var n: Int def __init__(out self, n: Int): self.n = n ``` Mojo rejects `Pinned` value transfers. Immovable types are useful when a value must remain at a stable memory address. For example: - Other values hold pointers to it, making its address part of its identity. Moving it would leave those pointers dangling. - The value contains a pointer to itself. Moving its bits would leave the interior pointer referring to the old, invalid address. - An external system tracks the value by address while an operation is in progress. Moving it would invalidate that association. --- ## Compile-time evaluation To understand Mojo's metaprogramming, you need to understand how Mojo runs code at compile time. Several things can trigger compile-time code execution: - Assigning an expression to a `comptime` value. - Evaluating a `comptime` conditional or loop. - Assigning an expression to a compile-time parameter. - And a few less common cases, all identified with the `comptime` keyword. Here are some examples: ```mojo comptime SIZE = 1024 // 32 ``` Here the expression `1024 // 32` invokes the `IntLiteral.__floordiv__()` method. Since it occurs in a `comptime` assignment, the method must be run at compile time. ```mojo comptime for i in range(4): print(i) ``` Here the `range(4)` function needs to run to produce an iterator for the `comptime for` statement. ```mojo var array = Array[Int, get_array_size()]() ``` In this example, the `get_array_size()` function needs to run at compile time to determine the `length` parameter, which forms part of the type of `array`. (For example, if `get_array_size()` returns 32, the type of the `array` variable is `Array[Int, 32]`.) When the compiler encounters a function call in a compile-time context, the compiler runs the function separately, as if it was a small separate program. This is similar in concept to how C++ evaluates a `constexpr`. (For a slightly deeper look at this process, see [How the compiler runs code](#how-the-compiler-runs-code).) While most code can run at compile time, Mojo won't run code that depends on the execution environment. The following are examples of code that Mojo won't run at compile time: - File I/O. - Foreign function calls (for example, to external libraries). - Functions that can [raise errors](/docs/manual/functions/#raising-and-non-raising-functions). In addition, the compiler can't run functions on the GPU. Compile-time functions in GPU code are actually run on the CPU. When running code, the compiler can allocate memory and instantiate types that allocate memory, such as strings and collections. With some limitations, it can pass compile-time values on to run-time code, a process called _materialization_. For more information, see the section on [materialization](/docs/manual/metaprogramming/materialization/). ## `comptime` values It is very common to want to _name_ compile-time values. Whereas `var` defines a runtime value, we need a way to define a named compile-time constant. For this, Mojo uses a `comptime` declaration. At its simplest, `comptime` can be used to define a constant value: ```mojo comptime rows = 512 ``` A `comptime` value is always evaluated at compile time, so you can use `comptime` to force a function to run at compile time. You can use this to calculate constant values based on information available at compile time, such as hardware parameters. ```mojo comptime block_size = _calculate_block_size() ``` Types are another common use for `comptime` values. Because types are compile-time expressions, you can use a `comptime` value as a shorthand (a type alias or "typedef") for a parameterized type: ```mojo comptime Float16 = SIMD[DType.float16, 1] comptime UInt8 = SIMD[DType.uint8, 1] var x: Float16 = 0 # Float16 works like a "typedef" ``` (These aliases and others are actually defined in the [`simd` module](/docs/std/simd/#comptime-values).) You can also parameterize a `comptime` value to express more complicated relationships. For details, see [Parameterized `comptime` values](/docs/manual/parameters/#parameterized-comptime-values). ### Compile-time scope Like `var` variables, `comptime` values obey scope, and you can use local `comptime` values within functions as you'd expect. Unlike `var` variables, `comptime` values can be defined at the module level, outside of any function. The following constructs create a new compile-time scope: - Functions. The body of a function creates a new compile-time scope. - Compile-time flow control. Each branch of a compile-time conditional creates its own scope. The body of a `comptime for` loop also creates its own scope. You can only assign a `comptime` value to a given identifier once in a given scope. ```mojo comptime VALUE = 10 def scope_me(): print(VALUE) # prints 10 comptime VALUE = 20 # comptime VALUE = 30 # error: invalid redeclaration of VALUE comptime if True: comptime VALUE = 40 print(VALUE) # prints 40 print(VALUE) # prints 20 ``` ## Compile-time flow control One of the simplest things you can do with metaprogramming is using compile-time flow control to conditionalize or repeat code. Some sample uses include: - Conditionalizing platform-specific code (CPU vs. GPU, Linux vs. macOS) without runtime overhead. - Unrolling loops to eliminate runtime branches. - Handling different data types in parameterized code. Unlike run-time flow control constructs, compile-time flow control constructs are evaluated once, at compile time, and determine what code is actually compiled. ### Compile-time conditionals {#comptime-if} You can add the `comptime` keyword to any `if` condition that's based on a valid compile-time expression (an expression that can be evaluated at compile time). This ensures that only the live branch of the `if` statement is compiled into the program, which can reduce your final binary size. For example: ```mojo from std.sys import has_accelerator def main(): comptime if has_accelerator(): run_on_gpu() else: run_on_cpu() ``` In this example, if no accelerator is available, the `run_on_gpu()` function is never called, or even compiled. The `comptime if` statement can include `elif` and `else` branches just like a standard `if` statement. ### Compile-time loop unrolling {#comptime-for} You can add the `comptime` keyword to a `for` loop to create a loop that's fully unrolled at compile time. You should generally use this only for loops with small loop bodies and low iteration counts. The loop sequence must be a valid compile-time expression (that is, an expression that can be evaluated at compile time). For example, if you use `for i in range(LIMIT)`, the expression `range(LIMIT)` defines the loop sequence. This is a valid compile-time expression if `LIMIT` is a parameter, `comptime` value, or integer literal. The compiler fully unrolls the loop by replacing the `for` loop with `LIMIT` copies of the loop body. The induction variable is replaced with a compile-time constant value for each "iteration." For example: ```mojo comptime for i in range(1, 5): b[i-1] = a[i] + a[i-1] ``` This is effectively unrolled to the following run-time code: ```mojo b[0] = a[1] + a[0] b[1] = a[2] + a[1] b[2] = a[3] + a[2] b[3] = a[4] + a[3] ``` This unrolled loop compiles to branchless machine code, unlike a normal `for` loop, which includes a bounds test at every iteration. This can be especially important on GPU, to avoid [thread divergence](https://max.modular.com/gpu/block-and-warp/#warp-level-synchronization). The `comptime for` construct unrolls at the beginning of compilation, which can greatly expand both the code size and the compilation time. ## How the compiler runs code The process of evaluating compile-time code involves three components of the compiler: - Parser. Parses the code into an intermediate representation (IR) and performs type checking. - Interpreter. Runs code at compile time. - Elaborator. Substitutes concrete values for compile-time parameters and produces concrete versions of parameterized functions and structs. When the parser turns code into IR, it also replaces some very simple `comptime` expressions with their values, a process called _constant folding_. For example, the compiler can constant fold the expression `2 + 3` to `5`. Standard library functions that are marked `@always_inline("builtin")` are constant foldable. Compile-time expressions that can't be constant folded persist and are evaluated in the elaborator. When the elaborator encounters a function call in a compile-time context, it invokes the interpreter to run the function. The interpreter then checks whether the function being called has already been _elaborated_ to produce a concrete, executable function. If not, the interpreter adds that function to the elaborator's work queue, and waits until it's done. Finally, the interpreter runs the concrete function—almost like it was a small separate program—and passes the return value back to the elaborator, which integrates it into the parsed IR. When reading code, it's important to remember that when a function is being interpreted at compile time, the function has been concretized: compile-time conditionals have been processed, and compile-time constraints and assertions have been tested. This sometimes _appears_ to contradict the expectation that your code runs in the order it appears in the function. For example, if your function includes a compile-time assertion that fails, compilation fails before the interpreter enters the function, so no part of the function is evaluated—even code that occurs _before_ the assertion. --- ## Comptime constraints and assertions Mojo's constraint system lets you express program guarantees that go beyond what the type system provides. This page explains how to use this system effectively. A constraint, defined with the `where` keyword, represents a precondition for calling a function or instantiating a struct: ```mojo def pow2[n: Int]() -> Int where n >= 0: ... ``` Here the `pow2()` function requires its `n` parameter to be greater than or equal to 0. The expression `n >= 0` is called a *proposition*. With the exception of very simple expressions, Mojo doesn't evaluate these propositions literally. Instead, it analyzes them symbolically, tracking a list of propositions that are known to be true in the current scope. Understanding this system of symbolic propositions is key to using constraints effectively. Mojo also supports *compile-time assertions*, which test a proposition at compile time—if the assertion evaluates to false, compilation fails: ```mojo comptime assert x >= 0, "x must be greater than or equal to 0." ``` ## Defining constraints You can use constraints in the following contexts: - At the end of a function, method, or in the parameters of struct declaration, to constrain the values that you can bind to one or more parameters. `def pow2[n: Int]() -> Int where n >= 0:` A single `where` clause can constrain multiple parameters: `def subspan[start: Int, end: Int](self) -> Self where end > start:` Methods can gate their availability on parameters of the parent struct: `def sort() where conforms_to(Self.T, Comparable):` - In the trait conformance list for a struct, to declare that a struct conforms to a trait only when certain conditions are met. `struct MyContainer[T: AnyType](Copyable where conforms_to(T, Copyable)):` You'll use this form, called *conditional trait conformance*, when defining parameterized types. For details, see the section on [conditional trait conformances](/docs/manual/generics/#conditional-trait-conformance). ## Symbolic propositions The constraint system works with *symbolic* propositions. ```mojo def first[size: Int](array: Array[_, size]) -> array.T where size > 0: ... ``` In this case, `size > 0` is a proposition that the constraint system tracks. By analogy, when you annotate a type on an argument you ask the compiler to ensure callers only pass that type. When you annotate a constraint on a function, you ask the compiler to ensure that the proposition is true at the call site. But the compiler can't test every proposition at every call site without running or interpreting unbounded amounts of code, which would vastly expand compile times. Instead, callers need to explicitly introduce "knowledge" into the constraint system. ### Introducing knowledge The compiler tracks knowledge by scopes: each scope contains a set of known true propositions, also known as "knowledge," and nested scopes accumulate knowledge from outer scopes. There are four ways to introduce knowledge to the system: - Inside a struct declaration, all constraints declared on the struct are known, because concrete instances of the struct type can only be created when the proposition is true: ```mojo struct List[size: Int] where size >= 0: # Knowledge base: # - `size >= 0` ``` - Inside a function, all constraints declared on the function are known, because callers can only call the function if they guarantee the constraint holds: ```mojo def create_list[size: Int]() -> List[size] where size >= 0: # Knowledge base: # - `size >= 0` ``` - Inside a `comptime if`, the `if` condition is known, because the code within the body is only instantiated if the condition is true: ```mojo comptime if size >= 0: # Knowledge base: # - `size >= 0` comptime if size.is_even(): # Knowledge base: # - `size >= 0` # - `size.is_even()` ``` - After a `comptime assert`, the asserted condition is known, because the compiler won't instantiate a function if a `comptime assert` condition is false. So any code after it is only instantiated if the assertion didn't fire: ```mojo comptime assert size >= 0 # Knowledge base: # - `size >= 0` comptime assert size.is_even() # Knowledge base: # - `size >= 0` # - `size.is_even()` ``` ### Satisfying constraints with knowledge Any time you call a function that has constraints, the system inspects the set of known-true propositions at the call site and determines whether *the known set of propositions* is a superset of *the required set of propositions* declared on the callee. The constraint system treats all propositions symbolically: it doesn't know or care what the expression means and doesn't interpret the code. With a few very simple exceptions (discussed later), the system doesn't perform symbolic math on your behalf. Instead, it treats these propositions as opaque and only uses knowledge you've explicitly introduced in the calling scope. ```mojo # A function that wants a non-empty list. def print_first[size: Int](l: Array[Int, size]) where size >= 1: ... # A wrapper that wants a size >= 2 list. def print_first_two[size: Int](l: Array[Int, size]) where size >= 2: # Error: invalid call to 'print_first': lacking evidence to prove # correctness print_first[size](l) # ... ``` Mojo checks constraints without knowing all the call sites, and these expressions can reference symbolic parameter values-for example `is_prime(x)` where `x` is unknown. Because of this, Mojo can't interpret `is_prime()` for all possible values of `x`, nor can it make logical deductions. For example if `is_prime(x)` and `x > 2` are true, it can't deduce that `is_odd(x)` must also be true. ## Compile-time assertions Use `comptime assert` to introduce a known-true proposition at a specific point in the code: ```mojo comptime assert x > 0, "x must be greater than 0." ``` The message is optional. If the condition evaluates to false at compile time, compilation fails and the compiler shows the message (or a default message if none is specified). Mojo adds the asserted condition to the list of "known true" propositions for any code following the assertion. Constraints and assertions serve complementary roles: a `where` clause exposes a *requirement* that callers must prove, and `comptime assert` is one way to satisfy that requirement. ## Limited evaluation of propositions In general, it's safe to assume that the constraint system doesn't evaluate propositions directly, and that all knowledge needs to be provided explicitly. However, the constraint system does apply a *very limited* amount of "smartness" for very common cases. These are provided as a convenience and should not be treated as the norm. The goal is to provide a consistent, predictable experience so users can recognize these patterns as they become more familiar with the system. ### Simple implication A known proposition of the form `A and B` can satisfy a requirement of `A` by itself (or `B` by itself), even though symbolically they aren't identical propositions. ```mojo def create_list[T: Copyable, size: Int]() -> List[T] where size >= 0: return List[T](capacity = size) def create_even_list[T: Copyable, size: Int]() -> List[T] where ( size >= 0 and (size / 2) * 2 == size ): # No need to individually prove `size >= 0` — it's part of the # `and` above, so the call to `create_list` type-checks. return create_list[T, size=size]() def main(): var l1 = create_even_list[Int, 4]() print(len(l1) % 2) # Prints 0, since the list has an even number of elements. # var l2 = create_even_list[Int, 5]() # Won't compile: 5 isn't even. ``` Similarly, `A` implies `A or B` for any `B`. ### Canonicalization Sometimes there is more than one way to write the same expression. The system always simplifies expressions into a normal form internally, so expressions that aren't identical on the surface may be seen as identical by the system. The following are self-explanatory (assume `x: Int`): - `x > 0` == `x >= 1` - `x >= 2` == `not (x < 2)` - `x + x` == `2 * x` There's a special case for function calls. When invoking functions as part of a proposition, the system treats the entire function call as opaque. Only two identical function calls are the same. ```mojo def is_even(x: Int) -> Bool: return x % 2 == 0 def needs_even[x: Int]() where is_even(x): pass def forward_even_bad[x: Int]() where x % 2 == 0: # ERROR: Needs evidence for `is_even(x)`. needs_even[x]() def forward_even_good[x: Int]() where is_even(x): # SUCCESS needs_even[x]() ``` :::note Builtin functions Some functions in the standard library can be evaluated in `where` clauses. These functions implement simple operations on core types (for example, `Int` numerics), which are known to the compiler. These functions and can be inlined into the calling expression when called in a parameter context. However, there's no way to identify these builtin functions without looking at the source code, and whether a given function is builtin may change without notice. If you need a predicate that works transparently rather than opaquely, consider implementing it as a parametric comptime value instead, which is always inlined. ```mojo comptime is_even[x: Int]: Bool = x % 2 == 0 ``` ::: ### Context-free folding This is more of an extreme case of canonicalization than a separate category, but may catch people by surprise. Certain primitive operations on constants can be canonicalized into a new constant. For example: - `1 + 1` == `2` - `4 % 2` == `0` - `1 == 1` == `True` This extends to expressions with a mix of constants and non-constants: - `1 + x + 1` == `2 + x` Concretely, this means you may omit explicitly providing knowledge for propositions that are simple operations on constants: ```mojo def create_my_list() -> List[2]: # No need to provide evidence for `2 >= 0`. return create_list[2]() ``` ## Best practices The examples here focus on functions (declaring constraints when writing functions and satisfying constraints when calling functions), but the tips generalize to other forms of constraints such as struct parameter constraints and conditional trait conformances. ### Writing functions When writing a function, how do you decide whether a `where` clause is right for you?
![](../images/metaprogramming/constraint-decisions.png#light) ![](../images/metaprogramming/constraint-decisions-dark.png#dark)
Figure 1. Deciding when to use constraints
#### Q1: Does your function handle the entire domain of its input types? If you can write your function so that it returns a result or throws an error on all inputs, you don't need to care about constraints at all. Just write your function as usual. For example, the following functions do *not* need constraints since they're guaranteed to return or throw: ```mojo def create_list_opt[size: Int]() -> Optional[List[size]]: comptime if size < 0: return None return ... def create_list_raise[size: Int]() raises -> List[size]: comptime if size < 0: raise Error("negative size!") return ... ``` Constraints are only for cases where you don't want to check for exceptional cases at execution time. - Constraints ask the type checker to rule out exceptional cases so that a type-checked program guarantees you (as the function author) don't need to handle these cases in your function body using run-time resources. - As always, enforcing static guarantees isn't free. You're trading off run-time error checking logic for compile-time proof writing. In the absence of hard limits that prevent you from error checking at run time, it comes down to your preference for user experience. #### Q2: Is this limitation a central concept of your code? If the condition represents a concept that is central to your code, a dedicated type may be easier than sprinkling `where` everywhere. Examples: - A SIMD library that needs to represent SIMD width values, which aren't arbitrary integers. - A filesystem library that needs to represent paths that follow specific format rules. - A network library that needs to represent port numbers, which must be in the valid range. This is a good approach because the constraint is proven once (at construction) and the refined value can be passed around unconstrained until it needs to be disassembled. Your APIs stay simpler: fewer `where` clauses, fewer repeated asserts. A new type doesn't come for free though, as it's no longer freely interoperable with the original type. Make sure that the semantics are distinct enough to warrant the extra code. For example, writing a new type usually means implementing dunder methods corresponding to common operators (addition, subtraction, equality, and so on), which preserve the semantics of the new type. #### Q3: Is the constraint understandable and provable by the caller? If yes, use constraints. Make the condition part of the function's contract so that callers must prove it. This tends to be the right choice when: - The condition is a user-facing requirement that the user can understand. - Callers typically already need to handle the "bad" case themselves (for example, they may already have a `comptime if` that branches on this condition). Example: ```mojo def take_prefix[n: Int, len: Int](...) -> ... where 0 <= n <= len: ... ``` One practical heuristic: if a user can read the function signature and immediately understand *what they did wrong* when the constraint fails, `where` is likely the right tool. If the constraint is *not* understandable or *not* provable by the user, use `assert` or `abort`. If the "bad case" isn't something users would understand, adding a `where` constraint only causes more confusion, as users can't reasonably prove it themselves. - This typically happens when your library returns a value that has some internal constraints on it and expects users to pass it back with those constraints. For example, a communication library passes a device handle to the user as an `Int`, which has properties that are only known internally (for example, non-negative, special bits). Library APIs accepting this handle can't expect the user to prove these. - There are usually more type-safe ways to achieve the same thing, so only do this if you don't care about type safety. For example, a more type-safe approach is to introduce a new type for the device handle so users are less likely to accidentally pass another `Int` or modify the returned value unexpectedly. #### Summary - Use a dedicated type when the condition is a common refinement that should be proven once and reused everywhere. - Use `where` when the condition is a user-understandable precondition. - Use `comptime assert` or `abort` for internal inconsistencies in your library where a user can't do anything with the failure. ### Calling functions When calling a function, how do you show proof that you've satisfied the constraint? This is where the constraint system actively guides you into handling exceptional cases.
![](../images/metaprogramming/constraint-propagation.png#light) ![](../images/metaprogramming/constraint-propagation-dark.png#dark)
Figure 2. Using constrained APIs
#### Q1: Do the constrained parameters come from a parent parameter list? If the constrained parameter you're passing is from the function's parameter list, or is a parameter on the enclosing struct, you're basically "forwarding" a value from one parameter list to another. Go to Q2. If not, you computed the value inside the function body, and you're in "construction" territory. Go to Q3. #### Q2: Does the same limitation apply to your function? If you're forwarding a constrained parameter into another constrained API, it's usually a hint to **propagate** the same requirement onto your own function. For example, to propagate the constraint to your callers: ```mojo def create_list[T: Copyable, size: Int]() -> List[T] where size >= 0: return List[T](capacity = size) # This function is also only meaningful when size >= 0. # Make that part of the contract too. def create_list_and_process[T: Copyable, size: Int](value: T) where size >= 0: comptime xs = create_list[T, size=size]() ... ``` But if you want your function to accept a *wider* input domain, your job is to **handle both cases** explicitly using a `comptime if`. For example, narrow the domain by checking before the call: ```mojo def create_list_and_fill[size: Int]() -> Optional[List[size]]: comptime if size >= 0: return needs_nonneg[size]() else: return None ``` #### Q3: Do you know that the parameter already satisfies this condition? If the parameter didn't come from a parent parameter list, it must have been computed in the body. Decide whether the desired constraint **holds by construction**. If it does, indicate this to the constraint system via a `comptime assert`. Note that this is you explicitly taking over the burden of proof. Don't be afraid to write these asserts. The symbolic nature of the constraint system means that it is conservative—logical deductions that are obvious to you aren't always "obvious" to it. Adding `comptime assert` is not a code smell, but rather an inseparable part of working within the constraint system. For example, given a computed parameter that is always valid: ```mojo # The constraint on hi & lo guarantees a valid size. # Introduce this piece of knowledge explicitly. comptime size = hi - lo comptime assert size >= 0, "span is guaranteed non-negative" return create_list[size]() ``` But if the constraint does *not* necessarily hold, insert a `comptime if` and handle both cases explicitly. For example, a computed parameter that may be invalid: ```mojo # This version does NOT have a constraint on its inputs. # Branch on the computation to handle both cases. def create_list_from_span[lo: Int, hi: Int]() -> Optional[List[hi - lo]]: comptime size = hi - lo comptime if size >= 0: return create_list[size]() else: return None ``` ## Summary - Constraints are part of the API contract. - A `where` clause is a precondition that the **API author** requires the **caller** to prove. - The compiler doesn't automatically derive evidence for callers. - The caller must explicitly provide evidence that the precondition is always satisfied. - If a caller gets a "lacking evidence" error, they can: 1. Add a constraint to push the requirement onto their own callers. 2. Branch on the condition (`comptime if`). 3. Assert an invariant (`comptime assert`). --- ## Intro to metaprogramming Many languages have facilities for *metaprogramming*: writing code that generates or modifies code. Python has facilities for dynamic metaprogramming: features like decorators, metaclasses, and many more. These features make Python very flexible and productive, but since they're dynamic, they come with run-time overhead. Other languages have static or compile-time metaprogramming features, like C preprocessor macros and C++ templates. These can be limiting and hard to use. Mojo's compile-time metaprogramming system uses the same language as run-time programs, so you don't have to learn a new language—just a few new features. The primary features you'll need to learn are: - Compile-time statements and expressions - Parameters - Traits ## Compile-time statements and expressions The `comptime` keyword identifies a statement or expression that needs to be evaluated at compile time. For example, the `comptime` keyword is used to declare compile-time constant values and to introduce compile-time conditionals and loops. For information on compile-time assignments and control flow, see [Compile-time evaluation](/docs/manual/metaprogramming/comptime-evaluation/). ## Parameters {#parameters-and-generics} Functions and structs can be *parameterized* with compile-time parameters, allowing you to define a container that holds different data types, or a matrix multiplication algorithm that's parameterized by the matrix dimensions. Compile-time parameters are similar to C++ template parameters or Rust generic parameters. At compile time, Mojo *specializes* parameterized code to make *concrete* versions—that is, it replaces parameters with constant values. For example, a matrix multiplication function parameterized on its matrix dimensions can be specialized at compile time to select the most efficient algorithm based on those dimensions. For information on parameterization, see [Parameters](/docs/manual/parameters/). ## Traits {#traits-and-generics} Type-parameterized functions and structs work across many types. For example, a list might hold `Int`, `Float32`, or `String` values. Type-parameterized code needs to know what operations those types support. A *trait* defines a set of behaviors that types provide. Instead of pre-selecting specific types, a parameterized sort function can just require `Comparable`. For more information, see [traits](/docs/manual/traits/) and [parameterized declarations](/docs/manual/generics/). --- ## Materializing compile-time values at run time Mojo's compile-time metaprogramming makes it easy to make calculations at compile time for later use. The process of making a *compile-time value* available at run time is called *materialization*. For types that can be trivially copied, this isn't an issue. The compiler can simply insert the value into the compiled program wherever it's needed. ```mojo comptime threshold: Int = some_calculation() # calculate at compile time for i in range(1000): my_function(i, threshold) # use value at runtime ``` However, Mojo also allows you to create instances of much more complex types at compile-time: types that dynamically allocate memory, like `List` and `Dict`. Re-using these values at run time presents some questions, like where the memory is allocated, who owns the values, and when the values are destroyed. This page describes when Mojo materializes values, and presents some techniques for avoiding unnecessary materialization of complex values. ## Implicit and explicit materialization When you use a `comptime` value at run time, you're explicitly or implicitly copying the value into a run-time variable: ```mojo comptime comptime_value = 1000 var runtime_value = comptime_value ``` This process of moving a compile-time value to a run-time variable is called *materialization*. If the value is implicitly copyable, like an `Int` or `Bool`, Mojo treats it as *implicitly materializable* as well. But types that **aren't** implicitly copyable present other challenges. Consider the following code: ```mojo def lookup_fn(count: Int): comptime list_of_values: List[Int] = [1, 3, 5, 7] for i in range(count): # Some computation, doesn't matter what it is. var idx = dynamic_function(i) # Look up another value var lookup = list_of_values[idx] # Use the value process(lookup) ``` This looks reasonable, but compiling it produces an error on this line: ```mojo var lookup = list_of_values[idx] ``` ```output cannot materialize comptime value of type 'List[Int]' to runtime because it is not 'ImplicitlyCopyable' ``` Just like Mojo forces you to explicitly copy a value that's expensive to copy, it forces you to explicitly materialize values that are expensive to materialize, by calling the [`materialize()`](/docs/std/builtin/value/materialize/) function. Here's the code above with explicit materialization added: ```mojo def lookup_fn(count: Int): comptime list_of_values: List[Int] = [1, 3, 5, 7] for i in range(count): var idx = dynamic_function(i) # This is the problem var tmp: List[Int] = materialize[list_of_values]() var lookup = tmp[idx] # tmp is destroyed here process(lookup) ``` This code materializes the list of values *inside* of the loop, which includes dynamically allocating heap memory and storing the four elements into that memory. Because the last use of `tmp` is on the next line, the memory then gets deallocated before the loop iterates. This creates and destroys the list on every iteration of the loop, which is clearly wasteful. A more efficient version would materialize the list *outside* of the loop: ```mojo def lookup_fn(count: Int): comptime list_of_values: List[Int] = [1, 3, 5, 7] var list = materialize[list_of_values]() for i in range(count): var idx = dynamic_function(i) var lookup = list[idx] process(lookup) # materialized list is destroyed here ``` This is why Mojo requires you to explicitly materialize non-trivial values; it puts you in control of when your program allocates resources. ## Global lookup tables Mojo doesn't currently have a general-purpose mechanism for creating global static data. This is a problem for some performance-sensitive code where you want to use a static lookup table. Even if you declare the table as a `comptime` value, you need to materialize it each time you want to use the data. The [`global_constant()`](/docs/std/builtin/globals/global_constant/) function provides a solution for storing a compile-time value into static global storage, so you can access it without repeatedly materializing the value. However, this currently only works for self-contained values which don't include pointers to other locations in memory. That rules out using collection types like `List` and `Dict`. The easiest way to use `global_constant()` is with [`Array`](/docs/std/collections/array/Array/), which allocates a statically sized array of elements on the stack. The following code uses `global_constant()` to create a static lookup table. ```mojo from std.builtin.globals import global_constant def use_lookup(idx: Int) -> Int64: comptime numbers: Array[Int64, 10] = [ 1, 3, 14, 34, 63, 101, 148, 204, 269, 343 ] ref lookup_table = global_constant[numbers]() if idx >= len(lookup_table): return 0 return lookup_table[idx] def main(): print(use_lookup(3)) ``` At compile time, Mojo allocates the `numbers` array, and then the `global_constant()` function copies it into static constant memory, where the code can reference it without requiring any dynamic logic to create or populate the array. At run time, the `lookup_table` identifier receives an immutable reference to this memory. Note the use of `ref lookup_table` to bind the reference returned by `global_constant()`. Using `var lookup_table` would cause a compiler error, because it would trigger a copy, and `Array` doesn't support implicit copying. ## Using the `comptime` keyword Another approach that you can use to avoid materializing a complex value is to use the `comptime` keyword to control when Mojo evaluates an expression. Assigning an expression to a `comptime` value causes Mojo to evaluate the expression at compile time. For example, if you want to force a function to run at compile time: ```mojo comptime tmp = calculate_something() # executed at compile time var y = x * tmp # executed at run time ``` If you're only creating a `comptime` value for a single use, you can use a `comptime` sub-expression instead: ```mojo var y = x * comptime (calculate_something()) ``` This works exactly like the previous example, without creating a named temporary value. The `comptime` keyword here tells Mojo to evaluate the expression inside the parentheses (`calculate_something()`) at compile time. For example, you can use a `comptime` sub-expression when working with the `Layout` type, which determines how you store and retrieve data in a `LayoutTensor`. Materializing a `Layout` requires dynamic allocation, which isn't supported on GPUs. So calling this code on a GPU produces an error: ```mojo comptime layout = Layout.row_major(16, 8) var x = layout.size() // WARP_SIZE # Can't implicitly materialize layout ``` A `comptime` sub-expression fixes this issue: ```mojo comptime layout = Layout.row_major(16, 8) var x = comptime (layout.size()) // WARP_SIZE ``` Now, the expression `layout.size()` gets evaluated at compile time, so there's no need to materialize the layout. You could also achieve the same effect using a named `comptime` value. ```mojo comptime layout = Layout.row_major(16, 8) comptime layout_size = layout.size() var x = layout_size // WARP_SIZE ``` The `comptime` sub-expression is just a more compact way to express the same thing. ## Materializing literals Literal values, like string literals and numeric literals are also materialized to their run-time equivalents, but this is mostly handled automatically by the compiler: ```mojo comptime str_literal = "Hello" # at compile time, a StringLiteral var str = str_literal # at run time, a String. var static_str: StaticString = str_literal # or a StaticString ``` Both `String` and `StaticString` can be implicitly created from a `StringLiteral`, but without a type annotation, Mojo defaults to materializing `StringLiteral` as a `String`. --- ## Reflection Reflection helps you write code that inspects its own structure at compile time and reports information about types. This makes it possible to build features like structural validation, automatic comparisons, serialization, safer assertions, and richer error messages without hardcoding details for specific type implementations. :::caution Mojo reflection is newly introduced and currently incomplete. Some reflection capabilities are limited, unstable, or not yet fully exposed through the language interface. This page describes the direction of the feature as well as the parts that are available today. All examples reflect the state of the language at the time this page was published. They may change as reflection support matures. ::: ## Why reflection? Reflection is one of Mojo's powerful compile-time features. It lets you inspect types, access fields, and generate code that adapts to a struct's shape. For example, define a struct, conform it to `Equatable`, and the `==` operator automatically works: ```mojo @fieldwise_init struct Sensor(Equatable, Hashable, Writable): var id: Int var label: String var reading: Float64 ``` This code uses no operator overload or boilerplate. Mojo inspects the struct at compile time, checks that each field supports equality, and generates the comparison code. That is what reflection does. Reflection has no runtime cost. The compiler does the work up front and emits code as efficient as manually written code. :::note In this example, `Sensor` also conforms to `Hashable` and `Writable`. By conforming, instances work as `Dict` keys or `Set` elements, and print cleanly, without extra code. ::: ## Inspect a type Use the `reflect[T]` alias to inspect a type at compile time. Built into Mojo, it resolves to `Reflected[T]`, a handle type with static methods for querying a type. This code uses `reflect[T]` to inspect a type's structure: ```mojo def show_type[T: AnyType](): comptime type_name = reflect[T].name() comptime field_count = reflect[T].field_count() comptime field_names = reflect[T].field_names() comptime field_types = reflect[T].field_types() print("struct", type_name) comptime for idx in range(field_count): comptime field_name = field_names[idx] comptime field_type = reflect[field_types[idx]].name() var intro = "├──" if idx < (field_count - 1) else "└──" print(intro, " var ", field_name, ": ", field_type, sep="") ``` Create some types to test this with: ```mojo @fieldwise_init struct MyStruct: var x: String var y: Optional[Int] comptime DefaultItemCount = 10 struct ParameterizedStruct[ T: Movable & Deinitable, item_count: Int = DefaultItemCount ]: var list: List[Self.T] def __init__(out self): self.list = List[Self.T](capacity=Self.item_count) def main(): show_type[MyStruct](); print() show_type[Optional[Float64]](); print() show_type[Dict[Int, String]](); print() show_type[ParameterizedStruct[String, item_count=5]]() ``` When run, this code prints each struct's name and fields with their types. The `comptime for` loop over fields resolves at compile time. At runtime, only the resulting `print()` calls execute: ```text struct tests.MyStruct ├── var x: String └── var y: std.collections.optional.Optional[SIMD[DType.int, 1]] struct std.collections.optional.Optional[SIMD[DType.float64, 1]] └── var _value: std.utils.variant.Variant[, {}] struct std.collections.dict.Dict[SIMD[DType.int, 1], String, \ std.hashlib._ahash.AHasher[[0, 0, 0, 0] : SIMD[DType.uint64, 4]]] ├── var _table: std.collections._swisstable.SwissTable[\ SIMD[DType.int, 1], String, std.hashlib._ahash.AHasher[\ [0, 0, 0, 0] : SIMD[DType.uint64, 4]]] └── var _order: List[SIMD[DType.int32, 1]] struct tests.ParameterizedStruct[String, 5 : SIMD[DType.int, 1]] └── var list: List[String] ``` The output uses compiler-resolved names, and the exact rendering may drift as the compiler evolves. Names appear with their parameters applied. Some print bare and others carry a module path: `Int`, `Float64`, `Bool`, `String`, and `List` show no path, while `Dict`, `Optional`, and `Set` show theirs. Your own structs carry the name of the module that declares them. Aliases resolve to what they alias, so `Int` and `Float64` show up as the one-element SIMD types underneath. Parameter values print with their type attached. If you only need the base type name, use `base_name()`: ```mojo print(reflect[List[Int]].base_name()) # List print(reflect[Dict[String, Int]].base_name()) # Dict ``` :::note When you need one field, access it by name instead of iterating: ```mojo comptime host_handle = reflect[Config].field["host"] var default_host: host_handle.T = "localhost" print(default_host) # localhost ``` Name-based lookup requires a concrete type. If `T` is parameterized, use index-based iteration instead. ::: ## Detect field-level changes between two values Compare two values and list which fields differ. Use this for test assertions, audit logs, change tracking, or debugging. ```mojo def diff_fields[T: AnyType](a: T, b: T) -> List[String]: comptime names = reflect[T].field_names() comptime types = reflect[T].field_types() var diffs = List[String]() comptime for idx in range(reflect[T].field_count()): comptime if conforms_to(types[idx], Equatable): ref a_val = reflect[T].field_ref[idx](a) ref b_val = reflect[T].field_ref[idx](b) if a_val != b_val: diffs.append(String(comptime (names[idx]))) return diffs^ ``` For example, consider a configuration type: ```mojo @fieldwise_init struct Config(Equatable): var host: String var port: Int var verbose: Bool var timeout: Float64 ``` `diff_fields()` compares two `Config` values and returns the field names that differ: ```mojo def main(): var old = Config("localhost", 8080, False, 30.0) var new = Config("localhost", 9090, True, 30.0) var changes = diff_fields(old, new) for name in changes: print("changed:", name) # changed: port # changed: verbose ``` ## Write once, reuse everywhere with traits Reflection is powerful when used in traits with provided methods. The method runs for any conforming struct that meets the trait requirements. `MakeCopyable` duplicates every copyable field from one instance to another: ```mojo trait MakeCopyable: def copy_to(self, mut other: Self): comptime field_count = reflect[Self].field_count() comptime field_types = reflect[Self].field_types() comptime Usable = Copyable & Deinitable comptime for idx in range(field_count): comptime field_type = field_types[idx] comptime if conforms_to(field_type, Usable): reflect[Self].field_ref[idx](other) = reflect[Self].field_ref[ idx ](self).copy() ``` Conforming structs receive `copy_to()` without writing an implementation. As a trait method, `copy_to()` has direct access to `Self`. You don't need a type parameter. ```mojo @fieldwise_init struct MultiType(MakeCopyable, Writable): var w: String var x: Int var y: Bool var z: Float64 def write_to[W: Writer](self, mut writer: W): writer.write(String(t"[{self.w}, {self.x}, {self.y}, {self.z}]")) def main(): var original = MultiType("Hello", 1, True, 2.5) var target = MultiType("", 0, False, 0.0) original.copy_to(target) print(target) # [Hello, 1, True, 2.5] ``` You define the behavior once. Every conforming struct gets it as a provided method. ## Layout, source locations, and type utilities These tools expose lower-level details such as layout, lifetimes, and source information. ### Field layout and byte offsets When you need field layout for zero-copy serialization, C interop, or alignment, use `field_offset()`: ```mojo struct Packet: var flags: UInt8 var id: UInt32 var payload: UInt64 def show_layout[T: AnyType](): var names = materialize[reflect[T].field_names()]() comptime for i in range(reflect[T].field_count()): comptime off = reflect[T].field_offset[index=i]() print(names[i], "at byte", off) def main(): show_layout[Packet]() # flags at byte 0 # id at byte 4 (alignment padding: 3 bytes) # payload at byte 8 ``` `field_offset` accepts `name=` or `index=` and accounts for alignment padding. The gap between `flags` (1 byte) and `id` (byte 4) shows the compiler inserting 3 bytes of padding so `id` aligns to a 4-byte boundary. ### Types and origins Two functions provide compile-time access to type and lifetime information from expressions: **`type_of(x)`** returns the type of an expression for use in parameter positions: ```mojo def make_default[T: Defaultable]() -> T: return T() def main(): var x = 42 var y = make_default[type_of(x)]() print(y) # 0 ``` **`origin_of(x)`** captures the origin (lifetime and mutability) of a reference. In Mojo, every reference has an _origin_ that tracks which value it reads from and whether it can mutate that value. `origin_of(x)` captures this information at compile time so you can thread it through function signatures. It appears in signatures where a returned reference must be tied to an input's lifetime: ```mojo from std.os import abort def first_ref[ T: Movable ](ref list: List[T]) -> ref[list[0]] T: if not list: abort("empty list") return list[0] def main(): var l: List[Int] = [1, 2, 3] ref x = first_ref(l) print(x) # 1 x += 10 # modifies the original list through the reference print(l) # [11, 2, 3] l = [] first_ref(l) # aborts with "empty list" ``` The returned reference shares its origin with `list`, so the compiler knows it is valid as long as `list` is. Both are available without imports. ### Source locations **`call_location()`** returns the caller's source location, not the location of the `call_location()` call itself. When building assertions or validators, error messages are more useful when they point to the call site: ```mojo from std.reflection import call_location @always_inline def require( cond: Bool, msg: String = "requirement failed" ) raises: if not cond: raise Error(call_location().prefix(msg)) def main() raises: var x = 5 require(x > 10, "x must be > 10") # Error: At /path/to/file.mojo:10:5: x must be > 10 ``` Mark the enclosing function `@always_inline` (or `@always_inline("nodebug")`). The decorator is what guarantees the location the function reports; without it, the result depends on whether the compiler inlined the call anyway. `call_location()` also accepts an optional `inline_count` parameter. The default (1) reports the immediate caller's call site. Higher values skip additional levels, and the compiler must inline every level in the chain. **`source_location()`** returns the location where `source_location()` is called. This is less useful for debugging because it reports the location of the call, not the caller: ```mojo from std.reflection import source_location def log(msg: String): var loc = source_location() print( "[", loc.file_name(), ":", loc.line(), "] ", msg, sep="" ) def main(): log("starting up") # [/path/to/file.mojo:4:15] starting up ``` ### Function names Retrieve a function's source name or linker symbol at compile time. Use this for logging, tracing, or dispatch: ```mojo from std.reflection import get_function_name, get_linkage_name def process_data(): pass def main(): print(get_function_name[process_data]()) # process_data print(get_linkage_name[process_data]()) # mangled symbol ``` - **`get_function_name[func]()`** returns the name as written in source code. - **`get_linkage_name[func]()`** returns the mangled symbol name. Both take the function as a parameter value. ## Learn more - Visit the reflection [package documentation](/docs/std/reflection/) for API details. - Learn more about [traits](/docs/manual/traits/). --- ## Operators Operators are symbols and keywords that act on values. They support addition, comparison, bitwise operations, and boolean logic using operator syntax instead of method calls. Mojo's operator syntax mirrors Python. Symbols, precedence, and associativity use Python conventions, so most behavior will feel familiar if you've used Python, C, Rust, or similar languages. That said, a few details are specific to Mojo. Boolean operators use words (`and`, `or`, `not`) instead of symbols like `&&` and `||`. The ternary expression places the condition in the middle. The caret (`^`) serves as both bitwise XOR and the transfer sigil for ownership and memory management. ## Arithmetic Standard arithmetic operators match what you see in most languages: ```mojo print(7 + 3) # 10, add print(7 - 3) # 4, subtract print(7 * 3) # 21, multiply ``` Exponentiation uses two stars (`**`) and not a caret (`^`). If you prefer a function form, call `pow(base, exponent)`: ```mojo print(2 ** 8) # 256 (exponentiation) ``` ### Unary symbols Three prefix operators apply to single values. Place the operator to the immediate left of the value without spaces: - `-x` negates (`-7`, the operator is `-`, the expression is `7`) - `+x` is a no-op identity (`+7`) - `~x` inverts bits (`var a: Int8 = -128; print(~a) # 127`) ### Division and remainder Mojo has two division operators, and the difference matters for negative numbers: ```mojo var a = -7 var b = 4 print(a / b) # -1 (truncates toward zero) print(a // b) # -2 (rounds toward negative infinity) ``` - Use `/` when you want truncation toward zero. - Use `//` when you want floor division. For floating-point types, `/` performs standard division and `//` returns a float rounded down to the nearest whole number. The modulo operator `%` returns the remainder, following this rule: ```text a == b * (a // b) + (a % b) ``` For example: ```mojo print(7 % 3) # 1 print(-7 % 4) # 1 print(7 % -4) # -1 ``` ### Exponentiation The `**` operator is right-associative, so it groups from the right: ```mojo print(2 ** 3 ** 2) # 512, same as 2 ** (3 ** 2) ``` Exponentiation is one of only two right-associative operators in Mojo. The other is the ternary conditional expression (`if`-`else`). ### Matrix multiplication The `@` operator performs matrix multiplication. If you've used NumPy, this will look familiar. Mojo doesn't include a built-in matrix type, but any type that implements `__matmul__()` can use it. ## Comparisons Mojo provides six comparison operators: `==`, `!=`, `<`, `<=`, `>`, and `>=`. Each returns a `Bool` value: ```mojo print(10 > 5) # True print(10 == 10) # True print(10 != 10) # False ``` ### Floating-point comparison Don't compare floating-point values with the equality operator (`==`). Small rounding errors accumulate, and values that look equal often aren't: ```mojo from std.math import isclose var total: Float64 = 0.0 for _ in range(10): total += 0.1 print(total == 1.0) # False ``` Use `isclose()` for approximate comparison on any floating-point type. ```mojo print(isclose(total, 1.0)) # True ``` ### Chained comparisons You can chain comparisons to check a range or a sequence of conditions in one expression. Each pair is evaluated from left to right: ```mojo var x = 5 print(1 < x < 10) # True, 1 < x and x < 10 print(1 < x < 3) # False, 1 < x and x < 3 print(1 < x <= 5 < 9) # True, 1 < x and x <= 5 and 5 < 9 ``` The expression `a < b < c` is equivalent to `(a < b) and (b < c)`. The middle value is evaluated once, not twice. This matters when the value comes from a function: ```mojo var short_item_list: List[Int] = [1, 2, 3, 4, 5] var ok = 0 < len(short_item_list) <= 10 print(ok) # True ``` Comparison, membership, and identity operators share the same precedence, so you can combine them in a single chain. As a hypothetical example: ```mojo 5 != a < b in c is d ``` This evaluates as: ```mojo (5 != a) and (a < b) and (b in c) and (c is d) ``` ## Bitwise operations Bitwise operators work on integer types at the bit level. They let you inspect and manipulate individual bits directly. AND (`&`) keeps bits that are set in both operands, OR (`|`) keeps bits set in either, and XOR (`^`) keeps bits that differ: ```mojo var flags: UInt8 = 0b0000_0101 var mask: UInt8 = 0b0000_0011 print(flags & mask) # 1 (only bit 0 set in both) print(flags | mask) # 7 (bits 0, 1, and 2) print(flags ^ mask) # 6 (bits 1 and 2 differ) ``` Left shift (`<<`) and right shift (`>>`) move bits by a given number of positions. Left shift by *n* is equivalent to multiplying by 2^n, right shift to dividing by 2^n: ```mojo print(1 << 4) # 16 print(16 >> 2) # 4 print(-16 >> 2) # -4 ``` Among the bitwise operators, precedence runs: NOT (`~`) is tightest, then shift operators, then AND, then XOR, then OR. If the grouping isn't obvious, use parentheses. :::caution Mojo uses the caret (`^`) for both bitwise XOR and the transfer operator. In expressions where the meaning could be ambiguous, Mojo treats it as XOR. For example, `x^+1` is `(x ^ (+1))`, not `((x^) + 1)`. ::: ## Boolean logic Mojo uses words for boolean operators instead of symbols like `&&` or `||`. The operators read like plain language: ```mojo print(True and False) # False print(True or False) # True print(not True) # False ``` ### Short-circuit evaluation The `and` and `or` operators stop as soon as the result is known. With `and`, if the left side is falsy, the right side isn't evaluated. With `or`, if the left side is truthy, the right side is skipped. ```mojo def always_true() -> Bool: print("called") return True # The string "called" never prints because the left side # of `and` is already False: print(False and always_true()) # False ``` This behavior is useful when the right side has side effects or is expensive to compute. ### Truthiness Types that conform to `Boolable` have a truth value, so they can be used directly in boolean expressions and `if` conditions. The rules are predictable: zero, empty strings, empty collections, and `None` are falsy. Everything else is truthy. ```mojo var name = "Mojo" if name: print("Name is set") ``` ## Membership and identity ### `in` and `not in` The `in` operator checks whether a collection contains a value: ```mojo var colors: List[String] = ["red", "green", "blue"] print("red" in colors) # True print("yellow" not in colors) # True ``` It also works with strings to check for substrings: ```mojo var food = "peanut butter" if "nut" in food: print("Contains a nut") # prints ``` ### `is` and `is not` Identity operators check whether two values refer to the same object, not just whether they are equal. The most common use is checking `Optional` values against `None`: ```mojo var opt: Optional[Int] = None if opt is None: print("No value") # prints opt = 42 if opt is not None: print("Has a value") # prints ``` ## String operators Strings support concatenation with `+` and repetition with `*`: ```mojo var greeting = "Hello" + " " + "Mojo" print(greeting) # Hello Mojo print("ha" * 3) # hahaha print("=" * 40) # a line of 40 equals signs ``` :::note When building a string from multiple values, the multi-argument `String()` initializer is more efficient than chaining `+`: ```mojo var result = String("Point (", x, ", ", y, ")") # or var result = String(t"Point({x}, {y})") ``` ::: Strings compare lexicographically. Uppercase letters sort before lowercase: ```mojo print("Zebra" < "ant") # True print("bird" == "bird") # True ``` ## Conditional expression Mojo uses `if`-`else` for conditional expressions instead of `? :`. The condition sits in the middle: ```mojo var score = 80 var result = "pass" if score > 65 else "fail" print(result) # pass ``` You can use this form anywhere an expression is valid, including function arguments: ```mojo def greet(name: String): print("Hello,", name) greet("Sami" if True else "Cass") # Hello, Sami ``` Like exponentiation, chained ternary expressions are right-associative. They group from the right, which can be hard to read: ```mojo var value = 50 var label = ( "low" if value < 10 else "high" if value > 100 else "mid" ) print(label) # mid ``` ## Assignment operators ### In-place assignment Most binary operators have a compound assignment form: `+=`, `-=`, `*=`, `/=`, `//=`, `%=`, `**=`, `@=`, `&=`, `|=`, `^=`, `<<=`, and `>>=`. These update the left-hand value instead of creating a new one: ```mojo var count = 0 count += 1 count += 1 print(count) # 2 var flags: UInt8 = 0b0000_0001 flags |= 0b0000_0100 print(flags) # 5 (bits 0 and 2 set) ``` For types that store data on the heap, in-place operators can avoid allocating intermediate values. A type must implement its in-place methods explicitly, so not every type that supports `+` also supports `+=`. ### Walrus operator The walrus operator (`:=`, officially an *assignment expression*) assigns a value inside an expression. The assigned value becomes the result of that expression. Press Return without entering text to terminate the loop. Non-empty strings are truthy: ```mojo while (var name := input("Name or return: ")): print("Hello,", name) ``` `input()` is a raising function, so call it from a raising function or handle its errors with `try`. ## Precedence When an expression mixes operators, precedence determines what runs first. From tightest to loosest, they are: calls and attribute access, exponentiation, unary prefix operators, arithmetic (multiply and divide before add and subtract), shifts, bitwise operators (AND before XOR before OR), comparisons, boolean logic (`not` before `and` before `or`), conditional expression, and the walrus operator. When in doubt, use parentheses. They cost nothing at runtime and make your intent clear both when you write the code and when it is later read and maintained: ```mojo # Clear without thinking about precedence var ready = (age >= 18) and (score > threshold) ``` Assignment operators (`=`, `+=`, `-=`, and others) are statements, not expressions. You can't mix them into expressions. --- ## 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](/docs/manual/python/mojo-from-python/)). If you want to package your project for distribution, instead see the [Packaging guide](/docs/tools/packaging/). ## 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: ```mojo title="mymodule.mojo" 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`: ```mojo title="main.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: ```mojo title="main.mojo" 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: ```mojo title="main.mojo" 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. :::note 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 [`mojo precompile`](/docs/cli/precompile) command—it can differ from the directory name). For examples, see the section below about [naming and identifiers](#package-naming-and-identifiers). For example, consider a project with these files: ```ini 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. :::note 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: ```mojo title="main.mojo" from mypackage.mymodule import MyPair def main(): var mine = MyPair(2, 4) mine.dump() ``` This immediately works: ```sh mojo main.mojo ``` ```output 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: ```sh mojo precompile mypackage -o mypack.mojoc ``` :::note A `.mojoc` file contains non-elaborated code, so you _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: ```ini main.mojo mypack.mojoc ``` Because we named the package `mypack`, we need to fix the import statement: ```mojo title="main.mojo" from mypack.mymodule import MyPair ``` And the code works the same: ```sh mojo main.mojo ``` ```output 2 4 ``` :::note 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: ```ini main.mojo mypackage/ __init__.mojo mymodule.mojo ``` Let's now add the following line in `__init__.mojo`: ```mojo title="__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: ```mojo title="main.mojo" 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 [`functional`](/docs/std/algorithm/functional/) module resides in the `std.algorithm` package, so you can import members of that module (such as the `map()` function) like this: ```mojo from std.algorithm.functional import map ``` However, the `algorithm/__init__.mojo` file also includes these lines: ```mojo title="algorithm/__init__.mojo" 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: ```mojo from std.algorithm import map ``` :::note Which modules in the standard library are imported to the package scope varies, and is subject to change. Refer to the [documentation for each module](/docs/std/) 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: ```mojo import `модул` import `package-with-hyphens and a space!` as package_without_hyphens_or_a_space def main(): `модул`.`здрасти`() package_without_hyphens_or_a_space.hello() ``` --- ## Parameterization Many programming languages offer systems for writing parameterized or polymorphic code, which let you write code once, and generate efficient, specialized code at compile time. Mojo's compile-time parameter system lets you define reusable code. A parameter is a compile-time input to a struct or function. Parameters appear in square brackets after the struct or function name. Parameters can take ordinary values, like `Int` or `String`: ```mojo def multiplier[factor: Int](x: Int) -> Int: return x * factor def main(): comptime times_ten = multiplier[10] var x10 = times_ten(3) ``` Parameters accept both types and values at compile time. When a parameter accepts a type, the result is type-parameterized code. When it accepts a value, the result is value-parameterized code. ```mojo no-test struct MyList[T: AnyType]: # ... type-parameterized struct FixedBuffer[size: Int]: # ... value-parameterized ``` Mojo's parameters are similar to C++ template parameters or Rust generic parameters. In Mojo, "parameter" always means a compile-time value, and "argument" always means a run-time value. In most other languages, a parameter is part of a declaration and an argument is the value you pass at the call site. Mojo changes the meaning of "parameter" to refer specifically to compile-time values. Mojo makes this distinction visible in syntax: use `[]` for parameters and `()` for arguments. In addition to parameterizing structs and functions, you can also define parameterized `comptime` values. ## Parameterized functions {#parameters-and-generics} To define a *parameterized function*, add parameters in square brackets ahead of the argument list. Each parameter is formatted just like an argument: a parameter name, followed by a colon and a type. In the following example, the function has a single parameter, `count` of type `Int`. ```mojo def repeat[count: Int](msg: String): comptime for i in range(count): print(msg) ``` The [`comptime`](/docs/manual/metaprogramming/comptime-evaluation/#comptime-for) keyword shown here causes the `for` loop to be fully unrolled at compile time. The `comptime for` requires the loop limits to be known at compile time. Since `count` is a parameter, `range(count)` can be calculated at compile time. Calling a parameterized function, you provide values for the parameters, just like function arguments: ```mojo repeat[3]("Hello") ``` ```output Hello Hello Hello ``` The compiler resolves the parameter values during compilation, and creates a concrete version of the `repeat[]()` function for each unique parameter value. After resolving the parameter values and unrolling the loop, the `repeat[3]()` function would be roughly equivalent to this: ```mojo no-test def repeat_3(msg: String): print(msg) print(msg) print(msg) ``` :::note This doesn't represent actual code generated by the compiler. By the time parameters are resolved, Mojo code has already been transformed to an intermediate representation in [MLIR](https://mlir.llvm.org/). ::: If the compiler can't resolve all parameter values to constant values, compilation fails. ### Overloading on parameters Functions and methods can be overloaded on their parameter signatures. For information on overload resolution, see [Overloaded functions](/docs/manual/functions/#overloaded-functions). ## Parameters at a glance Parameters to a function or struct appear in square brackets after a function or struct name. Parameters always require type annotations. When you're looking at a function or struct signature, you may see some special characters such as `/` and `*` in the parameter list. Here's an example: ```mojo def my_sort[ # infer-only parameters dtype: DType, width: SIMDLength, //, # positional-only parameter values: SIMD[dtype, width], /, # positional-or-keyword parameter compare: def(Scalar[dtype], Scalar[dtype]) thin -> Int, *, # keyword-only parameter reverse: Bool = False, ]() -> SIMD[dtype, width]: ``` Here, `compare` is a function-typed parameter. Because the comparator is a noncapturing function value, the function type explicitly uses `thin`. Here's a quick overview of the special characters in the parameter list: - Double slash (`//`): parameters declared before the double slash are [infer-only parameters](#infer-only-parameters). - Slash (`/`): parameters declared before a slash are positional-only parameters. Positional-only and keyword-only parameters follow the same rules as [positional-only and keyword-only arguments](/docs/manual/functions#positional-only-and-keyword-only-arguments). - A parameter name prefixed with a star, like `*Types` identifies a [variadic parameter](#variadic-parameters) (not shown in the example above). Any parameters following the variadic parameter are keyword-only. - Star (`*`): in a parameter list with no variadic parameter, a star by itself indicates that the following parameters are keyword-only parameters. - An equals sign (`=`) introduces a default value for an [optional parameter](#optional-parameters-and-keyword-parameters). ## Parameterized declarations Parameterization let functions work across multiple types, and let containers store values of many types. For example, [`List`](/docs/std/collections/list/List/) takes a type parameter, so `List[Int]` holds integers and `List[String]` holds strings. In Mojo, parameterizations use compile-time elements. A function parameterized on a type is type-parameterized. A function parameterized on a value is value-parameterized. Both use `[]`. This function uses both a type parameter and a value parameter: ```mojo def repeat[ MsgType: Writable, // infer-only count: Int ](msg: MsgType): comptime for _ in range(count): print(msg) def main(): repeat[2](42) # prints 42 on two lines ``` `MsgType` is type-parameterized. It accepts any `Writable` type. `count` is value-parameterized. It accepts a compile-time integer. Together, they let you write one function that works across types and specializes for different repeat counts. `MsgType` uses `//` to mark it as an [infer-only parameter](#infer-only-parameters). The compiler infers the type from `msg`, so you only pass `count` explicitly. For more on parameterized declarations, including trait conformance, conditional conformance, and value parameterizations, see [parameterized declarations](/docs/manual/generics/). ## Parameterized structs You can also add parameters to structs. You can use parameterized structs to build parameterized collections. For example, a parameterized array type might include code like this: ```mojo from std.memory.alloc import alloc, dealloc, ThinAllocation, Layout struct ParameterizedArray[T: Copyable & Deinitable]( Writable where conforms_to(T, Writable) ): var _data: ThinAllocation[Self.T] var _size: Int def __init__(out self, var *elements: Self.T): self._size = len(elements) self._data = alloc[Self.T]({count = self._size}).into_thin() var ptr = self._data.unsafe_ptr() for i in range(self._size): ptr.unsafe_offset(i).unsafe_write(elements[i].copy()) def __init__(out self, *, count: Int, value: Self.T): self._size = count self._data = alloc[Self.T]({count = count}).into_thin() var ptr = self._data.unsafe_ptr() for i in range(self._size): ptr.unsafe_offset(i).unsafe_write(copy=value) def __deinit__(deinit self): var ptr = self._data.unsafe_ptr() for i in range(self._size): ptr.unsafe_offset(i).unsafe_deinit_pointee() dealloc(self._data^.unsafe_with_layout({count = self._size})) def __getitem__(self, i: Int) raises -> ref[self] Self.T: if i < self._size: return self._data.unsafe_ptr().unsafe_origin_cast[ origin_of(self) ]()[unsafe_offset=i] else: raise Error("Out of bounds") def write_to( self, mut writer: Some[Writer] ) where conforms_to(Self.T, Writable): writer.write("[") var ptr = self._data.unsafe_ptr() for i in range(self._size): writer.write(ptr[unsafe_offset=i]) if i < self._size - 1: writer.write(", ") writer.write("]") ``` This struct has a single parameter, `T`, which is a placeholder for the data type you want to store in the array, sometimes called a *type parameter*. `T` conforms to the [`Copyable`](/docs/std/traits/copyable/Copyable/) trait and therefore to the [`Movable`](/docs/std/traits/movable/Movable/) trait. As with parameterized functions, you need to pass in parameter values when you use a parameterized struct. In this case, when you create an instance of `ParameterizedArray`, you need to specify the type you want to store, like `Int`, or `Float64`. (This is a little confusing, because the *parameter value* you're passing in this case is a *type*. That's OK: a Mojo type is a valid compile-time value.) You'll see that `Self.T` is used throughout the struct where you'd usually see a type name. For example, as the formal type for the `elements` in the initializer, and the return type of the `__getitem__()` method. Here's an example of using `ParameterizedArray`: ```mojo var array = ParameterizedArray(1, 2, 3) print(array) ``` ```output [1, 2, 3] ``` A parameterized struct can use the `Self` type to represent a concrete instance of the struct (that is, with all its parameters specified). For example, you could add a static factory method to `ParameterizedArray` with the following signature: ```mojo no-test struct ParameterizedArray[ElementType: Copyable & Deinitable]: ... @staticmethod def splat(count: Int, value: Self.T) -> Self: # Create a new array with count instances of the given value return Self(count=count, value=value) ``` Here, `Self` is equivalent to writing `ParameterizedArray[Self.ElementType]`. That is, you can call the `splat()` method like this: ```mojo var float_array = ParameterizedArray[Float64].splat(8, 0) ``` The method returns an instance of `ParameterizedArray[Float64]`. ### Referencing struct parameters As shown in the previous section, you reference a struct parameter using dot syntax, just like a struct method or field (for example, `Self.T`). This struct parameter access works anywhere, not just inside a struct's methods. You can access parameters as attributes on the type itself: ```mojo def on_type(): print(SIMD[DType.float32, 2].length) # prints 2 ``` Or as attributes on an *instance* of the type: ```mojo def on_instance(): var x = SIMD[DType.int32, 2](4, 8) print(x.dtype) # prints int32 ``` ### `comptime` members You can also define `comptime` values as members of a `struct` or `trait` declaration: ```mojo struct Circle[radius: Float64]: comptime pi = 3.14159265359 comptime circumference = 2 * Self.pi * Self.radius ``` These `comptime` members have a number of uses: - Constant values specific to the type. - Constant values calculated based on the struct's parameters. - Associated types based on the struct's parameters. The difference between parameters and `comptime` members is that parameter values are specified by the user, but `comptime` members represent either constant values or values derived from the input parameters. A required value is a `comptime` member without an initializer. Conforming types must provide a value for it. This is useful when the trait needs a compile-time constant that varies across conforming types. For example, a Measurable trait might require a `unit` string and an `always_positive` boolean to validate measurements. Common trait-provided values include constants (like seeds), and trait compositions for refinements. Referencing `comptime` members works just like referencing struct parameters. You can reference a member using dot syntax (such as `Self.IteratorType`). #### `comptime` members as enumerations Some Mojo types use `comptime` members to express enumerations. For example, the following code defines a `Sentiment` type that defines `comptime` constants for different sentiment values: ```mojo @fieldwise_init struct Sentiment(Equatable, ImplicitlyCopyable): var _value: Int comptime NEGATIVE = Sentiment(0) comptime NEUTRAL = Sentiment(1) comptime POSITIVE = Sentiment(2) def __eq__(self, other: Self) -> Bool: return self._value == other._value def __ne__(self, other: Self) -> Bool: return not (self == other) def is_happy(s: Sentiment): if s == Sentiment.POSITIVE: print("Yes. 😀") else: print("No. ☹️") ``` This pattern provides a type-safe enumeration. The [`DType`](/docs/std/builtin/dtype/DType/) struct implements a simple enum using `comptime` members like this. This allows clients to use values like `DType.float32` in parameter expressions or run-time expressions. #### `comptime` members as associated types Associated types are a common use for `comptime` members. For example, a `List[T]` struct holds values of type `T`. The list's `__iter__()` method returns a list iterator that returns values of type `T`. `List` uses a `comptime` member, `IteratorType`, to define the type of the returned iterator. The following code excerpt shows a simplified version of some of the `List` code, showing the `List` and its associated `IteratorType`: ```mojo no-test @fieldwise_init struct _ListIter[ mut: Bool, //, T: Copyable, origin: Origin[mut], ](ImplicitlyCopyable, Iterable, Iterator): comptime Element = Self.T # Required by the Iterator trait var index: Int var src: Pointer[List[Self.Element], Self.origin] # ... implementation omitted struct List[T: Copyable]( Boolable, Copyable, Defaultable, Iterable, Sized ): comptime IteratorType[ iterable_mut: Bool, //, iterable_origin: Origin[iterable_mut] ]: Iterator = _ListIter[Self.T, iterable_origin] # ... code omitted def __iter__(ref self) -> Self.IteratorType[origin_of(self)]: return {0, Pointer(to=self)} # ... code omitted ``` The `IteratorType` member is parameterized on an origin, so it can represent both mutable and immutable iterators. ### Struct methods A struct's method can take its own parameters. For example, the `SIMD.slice()` method takes a `size` parameter: ```mojo var m = SIMD[DType.int32, 4](1, 3, 5, 7) var n = m.slice[2]() print(n) # prints [1, 3] ``` A struct's lifecycle methods (`__init__()` and `__deinit__()`) are an exception to this rule—they can't take parameters. ### Case study: the SIMD type For a real-world example of a parameterized type, let's look at the [`SIMD`](/docs/std/simd/SIMD/) type from Mojo's standard library. [Single instruction, multiple data (SIMD)](https://en.wikipedia.org/wiki/Single_instruction,_multiple_data) is a parallel processing technology built into many modern CPUs, GPUs, and custom accelerators. SIMD allows you to perform a single operation on multiple pieces of data at once. For example, if you want to take the square root of each element in an array, you can use SIMD to parallelize the work. Processors implement SIMD using low-level vector registers in hardware that hold multiple instances of a scalar data type. To use the SIMD instructions on these processors, the data must be shaped into the proper SIMD width (data type) and length (vector size). Processors may support 512-bit or longer SIMD vectors, and support many data types from 8-bit integers to 64-bit floating point numbers, so it's not practical to define all of the possible SIMD variations. Mojo's [`SIMD`](/docs/std/simd/SIMD/) type (defined as a struct) exposes the common SIMD operations through its methods, and takes the SIMD data type and length values as parameters. This allows you to directly map your data to the SIMD vectors on any hardware. Here's a cut-down (non-functional) version of Mojo's `SIMD` type definition: ```mojo no-test struct SIMD[dtype: DType, length: Int]: var value: … # Some low-level MLIR stuff here # Create a new SIMD from a number of scalars def __init__(out self, *elems: SIMD[Self.dtype, 1]): ... # Fill a SIMD with a duplicated scalar value. @staticmethod def splat(x: SIMD[Self.dtype, 1]) -> SIMD[Self.dtype, Self.length]: ... # Cast the elements of the SIMD to a different elt type. def cast[target: DType](self) -> SIMD[target, Self.length]: ... # Many standard operators are supported. def __add__(self, rhs: Self) -> Self: ... ``` So you can create and use a SIMD vector like this: ```mojo var vector = SIMD[DType.int16, 4](1, 2, 3, 4) vector = vector * vector for i in range(4): print(vector[i], end=" ") ``` ```output 1 4 9 16 ``` As you can see, a simple arithmetic operator like `*` applied to a pair of `SIMD` vector operates on the corresponding elements in each vector. Defining each SIMD variant with parameters is great for code reuse because the `SIMD` type can express all the different vector variants statically, instead of requiring the language to pre-define every variant. Because `SIMD` is a parameterized type, the `self` argument in its functions carries those parameters—the full type name is `SIMD[dtype, length]`. Although it's valid to write this out (as shown in the return type of `splat()`), this can be verbose, so we recommend using the `Self` type (from [PEP673](https://peps.python.org/pep-0673/)) like the `__add__()` example does. ## Using parameterized types and functions You can use parameterized types and functions by passing values to the parameters in square brackets. For example, for the `SIMD` type above, `dtype` specifies the data type and `length` specifies the number of elements in the SIMD vector (which must be a power of 2): ```mojo # Make a vector of 4 floats. var small_vec = SIMD[DType.float32, 4](1.0, 2.0, 3.0, 4.0) # Make a big vector containing 1.0 in float16 format. var big_vec = SIMD[DType.float16, 32](1.0) # Do some math and convert the elements to float32. var bigger_vec = (big_vec + big_vec).cast[DType.float32]() ``` Note that the `cast()` method also needs a parameter to specify the type you want from the cast (the method definition above expects a `target` parameter value). Thus, just as the `SIMD` struct is a parameterized type definition, the `cast()` method is a parameterized method definition. At compile time, the compiler creates a concrete version of the `cast()` method with the target parameter bound to `DType.float32`. The code above shows the use of concrete types (that is, the parameters are all bound to known values). But the major power of parameters comes from the ability to define parameterized algorithms and types (code that uses the parameter values). For example, here's how to define a parameterized algorithm with `Scalar` that is datatype agnostic: ```mojo from std.math import sqrt def rsqrt[dt: DType](x: Scalar[dt]) -> Scalar[dt]: return 1 / sqrt(x) def main(): var v = Scalar[DType.float16](42) print(rsqrt(v)) ``` ```output 0.154296875 ``` When you write a type expression with square brackets, like `List[Int]`, you must bind (specify a value for) all of the type's parameters. There are two exceptions to this rule: - You can explicitly unbind one or more parameters to create a [partially-bound or unbound type](#partially-bound-and-unbound-types). - You can omit parameters if Mojo can infer them from context. ### Parameter inference The Mojo compiler can often *infer* parameter values, so you don't always have to specify them. For example, in the previous section, this is how we called the parameterized `rsqrt()` function: ```mojo var v = Scalar[DType.float16](42) print(rsqrt(v)) ``` The compiler infers the `dt` parameter based on the type of the `v` value passed into it, as if you wrote `rsqrt[DType.float16](v)` explicitly. Figure 1 shows a mental model for how parameter inference works.
![](../images/parameters/parameter-inference.png#light) ![](../images/parameters/parameter-inference-dark.png#dark)
Figure 1. Parameter inference
Parameter inference can seem a little confusing: it might seem like the compiler is inferring compile-time parameter values from run-time argument values. But in fact it's inferring parameters from the statically-known *types* of the arguments. :::note Inference failures If parameter inference fails, the compiler reports an error, usually "failed to infer parameter 'param_name'". Unfortunately, the compiler also sometimes reports this error incorrectly, for example, when the actual error is a type mismatch. In these cases, specifying the missing parameters explicitly often allows Mojo to report the correct error. ::: Mojo can also infer the values of struct parameters from the arguments passed to an initializer or static method. For example, consider the following struct: ```mojo struct One[Type: Writable & Copyable & Deinitable]: var value: Self.Type def __init__(out self, value: Self.Type): self.value = value.copy() def use_one(): var s1 = One(123) # equivalent to One[Int](123) var s2 = One("Hello") # equivalent to One[String]("Hello") ``` Note that you can create an instance of `One` without specifying the `Type` parameter—Mojo can infer it from the `value` argument. You can also infer parameters from a parameterized type passed to an initializer or static method: ```mojo struct Two[Type: Writable & Copyable & Deinitable]: var val1: Self.Type var val2: Self.Type def __init__(out self, one: One[Self.Type], another: One[Self.Type]): self.val1 = one.value.copy() self.val2 = another.value.copy() print(String(self.val1), String(self.val2)) @staticmethod def fire(thing1: One[Self.Type], thing2: One[Self.Type]): print("🔥", String(thing1.value), String(thing2.value)) def use_two() raises: var s3 = Two(One("infer"), One("me")) # prints: infer me Two.fire(One(1), One(2)) # prints: 🔥 1 2 # Two.fire(One("mixed"), One(0)) # Error: parameter inferred to two different values ``` `Two` takes a `Type` parameter, and its initializer takes values of type `One[Type]`. When constructing an instance of `Two`, you don't need to specify the `Type` parameter, since it can be inferred from the arguments. Similarly, the static `fire()` method takes values of type `One[Type]`, so Mojo can infer the `Type` value at compile time. Note that passing two instances of `One` with different types doesn't work. :::note If you're familiar with C++, you may recognize this as similar to Class Template Argument Deduction (CTAD). ::: ## Parameter declarations When you declare parameters on a struct or function, you have many of the same options as you have with arguments—you can define optional parameters with default values; keyword-only parameters; and variadic parameters. In addition, you can define *infer-only parameters*, which provide a flexible way of defining dependencies between parameterized types. ### Optional parameters and keyword parameters Just as you can specify [optional arguments](/docs/manual/functions#optional-arguments) in function signatures, you can also define an optional *parameter* by giving it a default value. You can also pass parameters by keyword, just like you can use [keyword arguments](/docs/manual/functions/#keyword-arguments). For a function or struct with multiple optional parameters, using keywords allows you to pass only the parameters you want to specify, regardless of their position in the function signature. For example, here's a function with two parameters, each with a default value: ```mojo def speak[a: Int = 3, msg: String = "woof"](): print(msg, a) def use_defaults(): speak() # prints 'woof 3' speak[5]() # prints 'woof 5' speak[7, "meow"]() # prints 'meow 7' speak[msg="baaa"]() # prints 'baaa 3' ``` Recall that when a parameterized function is called, Mojo can [infer the parameter values](#parameter-inference). That is, it can determine its parameter values from the parameters attached to an argument. If the parameterized function also has a default value defined, then the inferred parameter value takes precedence. For example, in the following code, we update the parameterized `speak[]()` function to take an argument with a parameterized type. Although the function has a default parameter value for `a`, Mojo instead uses the inferred `a` parameter value from the `bar` argument (as written, the default `a` value can never be used, but this is just for demonstration purposes): ```mojo @fieldwise_init struct Bar[v: Int]: pass def speak[a: Int = 3, msg: String = "woof"](bar: Bar[a]): print(msg, a) def use_inferred(): speak(Bar[9]()) # prints 'woof 9' ``` As mentioned above, you can also use optional parameters and keyword parameters in a struct: ```mojo struct KwParamStruct[greeting: String = "Hello", name: String = "🔥mojo🔥"]: def __init__(out self): print(Self.greeting, Self.name) def use_kw_params(): var a = KwParamStruct[]() # prints 'Hello 🔥mojo🔥' var b = KwParamStruct[name="World"]() # prints 'Hello World' var c = KwParamStruct[greeting="Hola"]() # prints 'Hola 🔥mojo🔥' ``` :::note Mojo supports positional-only and keyword-only parameters, following the same rules as [positional-only and keyword-only arguments](/docs/manual/functions#positional-only-and-keyword-only-arguments). ::: ### Variadic parameters Mojo also supports variadic parameters, similar to [Variadic arguments](/docs/manual/functions/#variadic-arguments): ```mojo struct MyTensor[*dimensions: Int]: pass ``` Variadic parameters currently have some limitations that variadic arguments don't have: - Variadic parameters must be homogeneous—that is, all the values must be the same type. - The parameter type must be register-passable. Variadic keyword parameters (for example, `**kwparams`) are not supported yet. ### Infer-only parameters Sometimes you need to declare functions where parameters depend on other parameters. Because the signature is processed left to right, a parameter can only *depend* on a parameter earlier in the parameter list. For example: ```mojo no-test def dependent_type[dtype: DType, value: Scalar[dtype]](): print("Value: ", value) print("Value is floating-point: ", dtype.is_floating_point()) dependent_type[DType.float64, Float64(2.2)]() ``` ```output Value: 2.2000000000000002 Value is floating-point: True ``` You can't reverse the position of the `dtype` and `value` parameters, because `value` depends on `dtype`. However, because `dtype` is a required parameter, you can't leave it out of the parameter list and let Mojo infer it from `value`: ```mojo no-test dependent_type[Float64(2.2)]() # Error! ``` Infer-only parameters are a special class of parameters that are **always** either inferred from context or specified by keyword. Infer-only parameters are placed at the **beginning** of the parameter list, set off from other parameters by the `//` sigil: ```mojo no-test def example[T: Copyable, //, list: List[T]]() ``` Transforming `dtype` into an infer-only parameter solves this problem: ```mojo def dependent_type[dtype: DType, //, value: Scalar[dtype]](): print("Value: ", value) print("Value is floating-point: ", dtype.is_floating_point()) ``` ```mojo dependent_type[Float64(2.2)]() ``` ```output Value: 2.2000000000000002 Value is floating-point: True ``` Because infer-only parameters are declared at the beginning of the parameter list, other parameters can depend on them, and the compiler always attempts to infer the infer-only values from bound parameters or arguments. There are sometimes cases where it's useful to specify an infer-only parameter by keyword. For example, the [`Span`](/docs/std/collections/span/Span/) type is parameterized on [origin](/docs/manual/values/lifetimes/): ```mojo no-test struct Span[mut: Bool, //, T: Copyable, origin: Origin[mut]]: # ... implementation omitted ``` Here, the `mut` parameter is infer-only. The value is usually inferred when you create an instance of `Span`. Binding the `mut` parameter by keyword lets you define a `Span` that requires a mutable origin. ```mojo def mutate_span(span: Span[mut=True, Byte, _]): for i in range(0, len(span), 2): if i + 1 < len(span): span.swap_elements(i, i + 1) ``` If the compiler can't infer the value of an infer-only parameter, and it's not specified by keyword, compilation fails. ## Parameter expressions are just Mojo code A parameter expression is any code expression (such as `a+b`) that occurs where a parameter is expected. Parameter expressions support operators and function calls, just like run-time code, and all parameter types use the same type system as the run-time program (such as `Int` and `DType`). Because parameter expressions use the same grammar and types as run-time Mojo code, you can use many ["dependent type"](https://en.wikipedia.org/wiki/Dependent_type) features. For example, you might want to define a helper function to concatenate two SIMD vectors: ```mojo def concat[ dtype: DType, ls_size: Int, rh_size: Int, // ](lhs: SIMD[dtype, ls_size], rhs: SIMD[dtype, rh_size]) -> SIMD[ dtype, ls_size + rh_size ]: var result = SIMD[dtype, ls_size + rh_size]() comptime for i in range(ls_size): result[i] = lhs[i] comptime for j in range(rh_size): result[ls_size + j] = rhs[j] return result ``` Note that the resulting length is the sum of the input vector lengths, and a simple `+` operation expresses this. ### Powerful compile-time programming While simple expressions are useful, sometimes you want to write imperative compile-time logic with control flow. You can even do compile-time recursion. For instance, here is an example "tree reduction" algorithm that sums all elements of a vector recursively into a scalar: ```mojo def slice[ dtype: DType, size: Int, // ](x: SIMD[dtype, size], offset: Int) -> SIMD[dtype, size // 2]: comptime new_size = size // 2 var result = SIMD[dtype, new_size]() for i in range(new_size): result[i] = Scalar[dtype](x[i + offset]) return result def reduce_add(x: SIMD) -> Int: comptime if x.length == 1: return Int(x[0]) elif x.length == 2: return Int(x[0]) + Int(x[1]) # Extract the top/bottom halves, add them, sum the elements. comptime half_size = x.length // 2 var lhs = slice(x, 0) var rhs = slice(x, half_size) return reduce_add(lhs + rhs) def main(): var x = SIMD[DType.int, 4](1, 2, 3, 4) print(x) print("Elements sum:", reduce_add(x)) ``` ```output [1, 2, 3, 4] Elements sum: 10 ``` This makes use of the [`comptime if`](/docs/manual/metaprogramming/comptime-evaluation/#comptime-if) statement, which is an `if` statement that runs at compile-time. It requires that its condition be a valid parameter expression, and ensures that only the live branch of the `if` statement is compiled into the program. This is similar to use of the `comptime for` loop shown earlier. ## Parameterized `comptime` values A *parameterized `comptime` value* is a compile-time expression that takes a list of parameters and returns a compile-time constant value: ```mojo comptime AddOne[a: Int] : Int = a + 1 comptime nine = AddOne[8] ``` As you can see in the previous example, a parameterized `comptime` value is a little like a *compile-time-only function*. A regular function or method can also be invoked at compile time: ```mojo def add_one(a: Int) -> Int: return a + 1 comptime ten = add_one(9) ``` A major difference between a function and a parameterized `comptime` value is that the value of a `comptime` expression can be a type, while a function can't return a type as a value. ```mojo no-test # Does not work—-dynamic type values not permitted def int_type() -> AnyType: return Int # Works comptime IntType = Int ``` Because a `comptime` value can be a type, you can use parameterized `comptime` values to express new types: ```mojo comptime TwoOfAKind[dt: DType] = SIMD[dt, 2] var twoFloats = TwoOfAKind[DType.float32](1.0, 2.0) comptime StringKeyDict[ValueType: Copyable & Deinitable] = Dict[ String, ValueType ] var b: StringKeyDict[UInt8] = {"answer": 42} ``` Parameterized `comptime` declarations support the same features as parameterized structs or functions: infer-only parameters, keyword-only and optional parameters, [automatic parameterization](#automatic-parameterization), and so on. ```mojo comptime Floats[size: Int, half_width: Bool = False] = SIMD[ (DType.float16 if half_width else DType.float32), size ] var floats = Floats[2](6.0, 8.0) var half_floats = Floats[2, True](10.0, 12.0) ``` ## Partially-bound and unbound types A parameterized type with its parameters specified is said to be *fully-bound*. That is, all of its parameters are bound to values. As mentioned before, you can only instantiate a fully-bound type (sometimes called a *concrete type*). However, parameterized types can be *unbound* or *partially bound* in some contexts. For example, you can use `comptime` to create a type alias to a partially-bound type to create a new type that requires fewer parameters: ```mojo comptime StringKeyDict = Dict[String, _] var b: StringKeyDict[UInt8] = {"answer": 42} ``` Here, `StringKeyDict` is a type alias for a `Dict` that takes `String` keys. The underscore `_` in the parameter list indicates that the second parameter, `V` (the value type), is unbound. You specify the `V` parameter later, when you use `StringKeyDict`. When used as a `comptime` value, any default values on unbound parameters are retained until a concrete type is formed (for example, by calling a struct's initializer): ```mojo @fieldwise_init struct HasDefault[x: Int, y: Int = 0]: pass comptime UseDefault = HasDefault[10] ``` ```mojo var instance1 = UseDefault() # instance of HasDefault[10, 0] ``` When defining parameterized APIs, you can use partially-bound and unbound types to express type constraints with less boilerplate, a feature called [automatic parameterization](#automatic-parameterization): ```mojo no-test # standard declaration--explicit parameter declarations def take_simd[dtype: DType, size: Int](value: SIMD[dtype, size]): pass # automatically parameterized declaration def take_floats(value: SIMD[_, _]): pass ``` You can specify a partially-bound or unbound type several ways: - Explicitly unbind one or more parameters using an underscore (`_`) in place of a parameter value: ```mojo comptime StringKeyDict = Dict[String, _] def take_floats(floats: SIMD[DType.float32, _]): pass ``` When writing a type expression like this, you must bind or explicitly unbind every parameter, unless Mojo can infer the parameter from context. For example, this produces an error: ```mojo comptime Bad = Dict[String] # error: 'Dict' failed to infer parameter 'V' ``` - Explicitly unbind an arbitrary number of parameters at the end of a parameter list using an ellipsis (`...`): ```mojo comptime PartiallyBound = SomeComplicatedType[String, ...] comptime Unbound = SomeComplicatedType[...] def take_simd(v: SIMD[...]): pass ``` Using an ellipsis unbinds any remaining parameters in the list, including keyword parameters. - Use the bare identifier with no square brackets to specify an unbound type: ```mojo comptime SomeAlias = SomeComplicatedType def take_simd2(v: SIMD): pass ``` As a matter of style, `SIMD[...]` or `SIMD[_, _]` is usually preferable to the bare `SIMD`. The former versions are more explicit, and provide a visual cue to readers that they're looking at a parameterized type. ### Partially-bound types versus parameterized comptime values You may notice that the `comptime` examples in this section look similar to the examples in the section on [parameterized `comptime` values](#parameterized-comptime-values). For example, you could define the `StringKeyDict` alias using either syntax: ```mojo no-test # partially-bound type comptime StringKeyDict = Dict[String, _] # parameterized comptime value comptime StringKeyDict[V] = Dict[String, V] ``` For simple type aliases, you can use either a partially-bound type or a parameterized `comptime` value. For more complex aliases, parameterized `comptime` values give you a great deal more flexibility. ## Automatic parameterization Writing heavily-parameterized APIs often produces long, repetitive signatures. For example, to define a function that takes any kind of `SIMD` value, you could write this: ```mojo no-test def take_simd[dtype: DType, size: Int, //](vec: SIMD[dtype, size]): pass ``` This signature represents a function that takes any `SIMD` value, inferring its `dtype` and `size` parameters. But it's a lot of code to do something pretty simple. To make it easier to write signatures like this, Mojo supports "automatic" parameterization. Instead of explicitly naming each parameter in the argument type, you specify a [partially-bound or unbound type](#partially-bound-and-unbound-types): ```mojo def take_simd(vec: SIMD[...]): print(vec.dtype) print(vec.length) ``` ```mojo var v = SIMD[DType.float64, 4](1.0, 2.0, 3.0, 4.0) take_simd(v) ``` ```output float64 4 ``` In the above example, the `take_simd()` function is automatically parameterized. The `vec` argument takes a value of type `SIMD[...]`—an unbound parameterized type. Mojo treats the unbound parameters on `vec` as infer-only parameters on the function. This is roughly equivalent to the following code: ```mojo no-test def take_simd[t: DType, s: Int, //](vec: SIMD[t, s]): print(t) print(s) ``` When you call `take_simd()` you must pass it a concrete instance of the `SIMD` type—that is, one with all of its parameters specified, like `SIMD[DType.float64, 4]`. The Mojo compiler *infers* the parameter values from the input argument. You can also use automatic parameterization with a partially bound type: ```mojo no-test def take_floats(floats: SIMD[DType.float32, _]): pass ``` There are two important differences between a manually-parameterized signature and an automatically-parameterized signature: - With a manually-parameterized function, you can access the parameters by name (for example, `t` and `s` in the previous example), which is not an option in an automatically parameterized function. However, you can always access a type's parameters and `comptime` members using dot syntax—as in the automatic parameterization example, which used `vec.dtype` and `vec.length` to access parameters on the argument. - With the manually-parameterized function, you can pass the parameter value directly; that's not an option with automatically-parameterized functions. The unbound parameters are always inferred. In addition to using automatic parameterization in the argument list of a function, you can also use it in the parameter lists of functions, structs, and parameterized `comptime` values. ### Examples of automatic parameterization This section shows more examples of using automatic parameterization. #### Automatic parameterization of parameters You can also take advantage of automatic parameterization in the parameter list of a function, struct, or parameterized `comptime` value. For example: ```mojo no-test def simd_param[value: SIMD[...]](): pass # Equivalent to: def simd_param[dtype: DType, size: Int, //, value: SIMD[dtype, size]](): pass ``` Here's another example using a parameterized `comptime` value: ```mojo comptime SomeComptime[s: SIMD[...]] = SomeStruct[s] # Equivalent to: comptime SomeComptime2[dtype: DType, size: Int, //, S: SIMD[dtype, size]] = SomeStruct[S] ``` #### Automatic parameterization and type expressions As previous examples showed, you can access the parameters of an argument or parameter value using dot syntax (`arg.param`). You can also use this syntax inside a signature. For example, if you want your function to take two SIMD vectors with the same type and size, you can write code like this: ```mojo def interleave(v1: SIMD[...], v2: type_of(v1)) -> SIMD[v1.dtype, v1.length * 2]: var result = SIMD[v1.dtype, v1.length * 2]() comptime for i in range(v1.length): result[i * 2] = v1[i] result[i * 2 + 1] = v2[i] return result ``` ```mojo var a = SIMD[DType.int16, 4](1, 2, 3, 4) var b = SIMD[DType.int16, 4](0, 0, 0, 0) var c = interleave(a, b) print(c) ``` ```output [1, 0, 2, 0, 3, 0, 4, 0] ``` As shown in the example, you can use the magic `type_of(x)` expression if you just want to match the type of an argument. In this case, it's more convenient and compact than writing the equivalent `SIMD[v1.dtype, v1.length]`. #### Automatic parameterization with partially-bound types Mojo also supports automatic parameterization: with [partially-bound parameterized types](#partially-bound-and-unbound-types) (that is, types with some but not all of the parameters specified). For example, suppose you have a `Fudge` struct with three parameters: ```mojo @fieldwise_init struct Fudge[sugar: Int, cream: Int, chocolate: Int = 7](Writable): pass ``` You can write a function that takes a `Fudge` argument with just one bound parameter (it's *partially bound*): ```mojo def eat(f: Fudge[5, ...]): print("Ate", f) ``` The `eat()` function takes a `Fudge` struct with the first parameter (`sugar`) bound to the value 5. The second and third parameters, `cream` and `chocolate` are unbound. The unbound `cream` and `chocolate` parameters become implicit parameters on the `eat` function. In practice, this is roughly equivalent to writing: ```mojo no-test def eat[cr: Int, ch: Int, //](f: Fudge[5, cr, ch]): print("Ate", String(f)) ``` In both cases, you can call the function by passing in an instance with the `cream` and `chocolate` parameters bound: ```mojo eat(Fudge[5, 5, 7]()) # Ate Fudge (5,5,7) eat(Fudge[5, 8, 9]()) # Ate Fudge (5,8,9) ``` If you try to pass in an argument with a `sugar` value other than 5, compilation fails, because it doesn't match the argument type: ```mojo no-test eat(Fudge[12, 5, 7]()) # This fails because `eat()` expects `Fudge[5, 5, 7]`, but this value is # `Fudge[12, 5, 7]`. ``` You can also explicitly unbind individual parameters. This gives you more freedom in specifying unbound parameters. For example, you might want to let the user specify values for `sugar` and `chocolate`, and leave `cream` constant. To do this, replace each unbound parameter value with a single underscore (`_`): ```mojo def devour(f: Fudge[_, 6, _]): print("Devoured", String(f)) ``` Again, the unbound parameters (`sugar` and `chocolate`) are added as implicit parameters on the function. You can also unbind parameters by keyword, or mix positional and keyword parameters, so the following function is roughly equivalent to the previous one: the first parameter, `sugar` is explicitly unbound with the underscore character. The `chocolate` parameter is unbound using the keyword syntax, `chocolate=_`. And `cream` is explicitly bound to the value 6: ```mojo no-test def devour(f: Fudge[_, chocolate=_, cream=6]): print("Devoured", String(f)) ``` Both versions of the `devour()` function work with the following calls: ```mojo devour(Fudge[3, 6, 9]()) devour(Fudge[4, 6, 8]()) ``` ```output Devoured Fudge (3,6,9) Devoured Fudge (4,6,8) ``` ## Assert parameterized type equality with `rebind()` One of the consequences of Mojo not performing function instantiation in the parser like C++ is that Mojo cannot always figure out whether some parameterized types are equal and complain about an invalid conversion. This typically occurs in static dispatch patterns. For example, the following code won't compile: ```mojo no-test def take_simd8(x: SIMD[DType.float32, 8]): pass def parameterized_simd[nelts: Int](x: SIMD[DType.float32, nelts]): comptime if nelts == 8: take_simd8(x) ``` The parser will complain: ```plaintext error: invalid call to 'take_simd8': argument #0 cannot be converted from 'SIMD[f32, nelts]' to 'SIMD[f32, 8]' take_simd8(x) ~~~~~~~~~~^~~ ``` This is because the parser fully type-checks the function without instantiation, and the type of `x` is still `SIMD[f32, nelts]`, and not `SIMD[f32, 8]`, despite the static conditional. The remedy is to manually assert the type of `x`, using the [`rebind()`](/docs/std/builtin/rebind/rebind/) builtin, which inserts a compile-time assertion that the input and result types resolve to the same type after elaboration: ```mojo def take_simd8(x: SIMD[DType.float32, 8]): pass def parameterized_simd[nelts: Int](x: SIMD[DType.float32, nelts]): comptime if nelts == 8: take_simd8(rebind[SIMD[DType.float32, 8]](x)) ``` The compiler still checks that the types match, but does so later, during elaboration. If the types don't match, compilation fails. There are fairly simple rules for when to use `rebind()`: - **Do** use `rebind()` when you know that two parametric types will be identical after elaboration. - **Don't** use `rebind()` to cast between arbitrary data types. `rebind()` returns a reference to the rebound value. If you need to transfer the rebound value or assign it to a variable, use the [`rebind_var()`](/docs/std/builtin/rebind/rebind_var/) function. --- ## Intro to pointers A pointer is an indirect reference to one or more values stored in memory. The pointer is a value that holds an address to memory, and provides APIs to store and retrieve values to that memory. The value pointed to by a pointer is also known as a _pointee_. The Mojo standard library includes several types of pointers, which provide different sets of features. All of these pointer types are _parameterized_—they can point to any type of value, and the value type is specified as a parameter. For example, the following code creates an `OwnedPointer` that points to an `Int` value: ```mojo from std.memory import OwnedPointer var ptr: OwnedPointer[Int] ptr = OwnedPointer(100) ``` The `ptr` variable has a value of type `OwnedPointer[Int]`. The pointer _points to_ a value of type `Int`, as shown in Figure 1.
![A local variable, ptr, points to an OwnedPointer[Int] which points to an Int pointee. The value of the OwnedPointer is the address of the Int pointee.](../images/pointers/owned-pointer-diagram.png#light) ![A local variable, ptr, points to an OwnedPointer[Int] which points to an Int pointee. The value of the OwnedPointer is the address of the Int pointee.](../images/pointers/owned-pointer-diagram-dark.png#dark)
Figure 1. Pointer and pointee
Accessing the memory—to retrieve or update a value—is called _dereferencing_ the pointer. You can dereference a pointer by following the variable name with an empty pair of square brackets: ```mojo # Update an initialized value ptr[] += 10 # Access an initialized value print(ptr[]) ``` ## Pointer terminology Before jumping into the pointer types, here are a few terms you'll run across. Some of them may already be familiar to you. - **Safe pointers**: are designed to prevent memory errors. Unless you use one of the APIs that are specially designated as unsafe, you can use these pointers without worrying about memory issues like double-free or use-after-free. - **Nullable pointers**: some languages use a sentinel value to represent a pointer that doesn't point to anything (a "null pointer"). None of the Mojo standard library pointer types are nullable. To model a nullable pointer, use the [`Optional`](/docs/std/collections/optional/Optional/) type. For example, `Optional[Pointer]` or `Optional[OwnedPointer]`. - **Owning pointers**: own their pointees, which means that the value they point to may be deallocated when the pointer itself is destroyed. Owning pointers (or _smart pointers_) are responsible for allocating and deallocating memory to hold their pointees. Non-owning pointers may point to values owned elsewhere, or may point to dynamically-allocated memory. - **Uninitialized memory**: refers to memory locations that haven't been initialized with a value, which may therefore contain random data. Newly-allocated memory is uninitialized. The safe pointer APIs don't let you access memory that's uninitialized. The unsafe APIs can access a block of uninitialized memory locations and then initialize them one at a time. Being able to access uninitialized memory is unsafe by definition. - **Copyability**: many pointer types can be copied implicitly (for example, by assigning a value to a variable): ```mojo var copied_ptr = ptr ``` The pointer itself is a small amount of data to copy (typically 64 bits), and copying the pointer doesn't copy the pointee—both the original pointer and the copy point to the same memory location and the same value. ## Pointer types The Mojo standard library includes several pointer types with different characteristics: - [`Pointer`](/docs/std/memory/pointer/Pointer/) is Mojo's primary pointer type. It points to one or more contiguous memory locations, and can refer to uninitialized memory. - [`OwnedPointer`](/docs/std/memory/owned_pointer/OwnedPointer/) is a smart pointer that points to a single value, and maintains exclusive ownership of that value. - [`ArcPointer`](/docs/std/memory/arc_pointer/ArcPointer/) is a reference-counted smart pointer that points to an owned value with ownership potentially shared with other instances of `ArcPointer`. Table 1 summarizes the different types of pointers:
| | `Pointer` | `OwnedPointer` | `ArcPointer` | |--------------------------------------------------|----------------------------|-------------------------|-------------------------| | Safe | Conditionally 1 | Yes | Yes | | Memory allocation | Manual via `alloc()` | Implicit 2 | Implicit 2 | | Owns pointee(s) | No 3 | Yes | Yes | | Implicitly copyable | Yes | No | Yes | | Nullable | No | No | No | | Can point to uninitialized memory | Yes | No | No | | Can point to multiple values (array-like access) | Yes | No | No |
Table 1. Pointer types
1 `Pointer` has both safe and unsafe methods. Unsafe methods are named with the `unsafe_` prefix (or require an `unsafe_` keyword argument). 2 `OwnedPointer` and `ArcPointer` implicitly allocate memory when you initialize the pointer with a value. 3 `Pointer` provides unsafe methods for initializing and destroying instances of the stored type. The user is responsible for managing the lifecycle of stored values. The following sections provide more details on each pointer type. ## `Pointer` The [`Pointer`](/docs/std/memory/pointer/Pointer/) type is Mojo's primary pointer type. It can access a block of contiguous memory locations, which might be uninitialized. Heap-allocated memory is accessed through a `Pointer`; the other pointer types wrap a `Pointer` to access heap memory. The `Pointer` type is _safe_ when used to point to an existing value: ```mojo var ptr = Pointer(to=some_value) print(ptr[]) ``` When used this way, the `Pointer` type carries the origin of the value it points to. It can be used to store a reference in a struct field. The `Pointer` type also provides a number of unsafe methods you can use to access dynamically-allocated memory, initialize and destroy stored values, and more. These features are useful for low-level systems programming tasks, but you need to use them with care. Some examples of _unsafe_ pointer uses include: - Building high-performance array-like collections, such as `List`. A single `Pointer` can access many values, and gives you a lot of control over how you allocate, use, and deallocate memory. Being able to access uninitialized memory means that you can preallocate a block of memory, and initialize values incrementally as they are added to the collection. - Interacting with external libraries including C++ and Python. You can use `Pointer` to pass a buffer full of data to or from an external library. For more information, see [Using pointers](/docs/manual/pointers/using-pointers). ## `OwnedPointer` The [`OwnedPointer`](/docs/std/memory/owned_pointer/OwnedPointer/) type is a smart pointer designed for cases where there is single ownership of the underlying data. An `OwnedPointer` points to a single item, which is passed in when you initialize the `OwnedPointer`. The `OwnedPointer` allocates memory and moves or copies the value into the reserved memory. ```mojo no-test from std.memory import OwnedPointer var o_ptr = OwnedPointer(some_big_struct^) ``` An owned pointer can hold almost any type of item, but when constructing an `OwnedPointer`, the stored item must be either `Movable` or `Copyable`. Since an `OwnedPointer` is designed to enforce single ownership, the pointer itself can be moved, but not copied. `OwnedPointer` does provide an initializer that creates a new `OwnedPointer` by copying the _stored value_ from an existing `OwnedPointer`. This results in two owned pointers, each with its own separate allocation and its own copy of the stored value. ## `ArcPointer` An [`ArcPointer`](/docs/std/memory/arc_pointer/ArcPointer/) is a reference-counted smart pointer, ideal for shared resources where the last owner for a given value may not be clear. Like an `OwnedPointer`, it points to a single value, and it allocates memory when you initialize the `ArcPointer` with a value: ```mojo from std.memory import ArcPointer var attributesDict: Dict[String, String] = {} var attributes = ArcPointer(attributesDict^) ``` Unlike an `OwnedPointer`, an `ArcPointer` can be freely copied. All instances of a given `ArcPointer` share a reference count, which is incremented whenever the `ArcPointer` is copied and decremented whenever an instance is destroyed. When the reference count reaches zero, the stored value is destroyed and the allocated memory is freed. You can use `ArcPointer` to implement safe reference-semantic types. For example, in the following code snippet `SharedDict` uses an `ArcPointer` to store a dictionary. Copying an instance of `SharedDict` only copies the `ArcPointer`, not the dictionary, which is shared between all of the copies. ```mojo from std.memory import ArcPointer struct SharedDict(ImplicitlyCopyable): var attributes: ArcPointer[Dict[String, String]] def __init__(out self): var attributesDict: Dict[String, String] = {} self.attributes = ArcPointer(attributesDict^) def __init__(out self, *, copy: Self): self.attributes = copy.attributes def __setitem__(mut self, key: String, value: String): self.attributes[][key] = value def __getitem__(self, key: String) -> String: return self.attributes[].get(key, default="") def main(): var thing1 = SharedDict() var thing2 = thing1 thing1["Flip"] = "Flop" print(thing2["Flip"]) ``` :::note `ArcPointer` makes the reference count itself thread-safe, but reads and writes to the stored value are not—callers are responsible for synchronization. ::: --- ## Using pointers The [`Pointer`](/docs/std/memory/pointer/Pointer/) struct is Mojo's primary pointer type for indirectly referencing locations in memory. You can use a pointer in many different ways: - As a safe, indirect reference to an existing owned value. (For example, the iterator for a collection might hold a pointer back to the original collection.) - As a pointer to a block of dynamically-allocated memory, to build array-like data structures. - As a raw memory location to pass to low-level interfaces or other programming languages. Some of these uses are safe, but others—particularly those involving dynamically-allocated memory—are *unsafe*: your code, not the compiler, is responsible for using the memory correctly. For a comparison of standard library pointer types, see [Intro to pointers](/docs/manual/pointers/). ## Pointer basics A `Pointer` is a type that holds an address to memory. You can store and retrieve values in that memory. The `Pointer` type is *parameterized*—it can point to any type of value, and the value type is specified as a parameter. The value pointed to by a pointer is sometimes called a *pointee*. ```mojo var count: Int = 0 # Point to an existing value var ptr = Pointer(to=count) # ptr's type is Pointer[Int, ...] # Mutate the value ptr[] = 100 ```
![A local variable, ptr, points to a Pointer[Int] holding the address 0x06f6a6f4d. An arrow leads from the Pointer to an Int pointee containing the value 100, whose memory address is 0x06f6a6f4d.](../images/pointers/pointer-diagram.png#light) ![A local variable, ptr, points to a Pointer[Int] holding the address 0x06f6a6f4d. An arrow leads from the Pointer to an Int pointee containing the value 100, whose memory address is 0x06f6a6f4d.](../images/pointers/pointer-diagram-dark.png#dark)
Figure 1. Pointer and pointee
Accessing the memory—to retrieve or update a value—is called *dereferencing* the pointer. You can dereference a pointer by following the variable name with an empty pair of square brackets: ```mojo # Update an initialized value ptr[] += 10 # Access an initialized value print(ptr[]) ``` ```output 110 ``` These two operations—creating a pointer to an existing value and dereferencing that pointer—are safe: the pointer maintains the ownership linkage to the original value, so Mojo can track the memory. Other operations, especially those involving dynamically-allocated memory, are generally *unsafe*, meaning that your code is responsible for: - allocating and deallocating memory - knowing whether a given memory location is initialized or uninitialized - manually calling deinitializers when a pointee is no longer being used Unsafe operations are prefixed with `unsafe_` or use a keyword argument prefixed with `unsafe_`. ## Lifecycle of a pointer At any given time, a pointer value can be in one of several states. It can be *uninitialized*, *dangling*, or point to a valid memory location which is either initialized or uninitialized: - Uninitialized. Just like any variable, a variable of type `Pointer` can be declared but uninitialized. ```mojo no-test var ptr: Pointer[Int, MutUntrackedOrigin] ``` - Pointing to allocated, uninitialized memory. The [`alloc()`](/docs/std/memory/alloc/alloc/) function allocates a block of memory with space for the specified number of elements of the pointee's type, and [`Allocation.unsafe_ptr()`](/docs/std/memory/alloc/Allocation/#unsafe_ptr) returns a pointer to that memory. ```mojo var allocation = alloc(Layout[Int].single()) var ptr = allocation.unsafe_ptr() ``` Trying to dereference a pointer to uninitialized memory results in undefined behavior. - Pointing to initialized memory. You can initialize an allocated, uninitialized pointer by moving or copying an existing value into the memory. Or you can construct a pointer to an existing value by calling the initializer with the `to` keyword argument. ```mojo no-test ptr.unsafe_write(value^) # or ptr.unsafe_write(copy=value) # or var ptr = Pointer(to=value) ``` Once the value is initialized, you can read or mutate it using the dereference syntax: ```mojo no-test var oldValue = ptr[] ptr[] = newValue ``` - Dangling. When you free the pointer's allocated memory, you're left with a *dangling pointer*. The address still points to its previous location, but the memory is no longer allocated to this pointer. Trying to dereference the pointer, or calling any method that would access the memory location, results in undefined behavior. ```mojo dealloc(allocation^) ``` The following diagram shows the lifecycle of a `Pointer`:
![A state diagram with four states. A pointer declared as var ptr: Pointer[T] starts uninitialized. From there, Allocation.unsafe_leak() or Allocation.unsafe_ptr() leads to a pointer to uninitialized memory, and Pointer(to=val) leads to a pointer to initialized memory. unsafe_write() moves a pointer from uninitialized to initialized memory, while unsafe_take_pointee() and unsafe_deinit_pointee() move it back. A pointer to initialized memory can be read or mutated in place. Calling dealloc() on the Allocation leaves a dangling pointer.](../images/pointers/pointer-lifecycle.png#light) ![A state diagram with four states. A pointer declared as var ptr: Pointer[T] starts uninitialized. From there, Allocation.unsafe_leak() or Allocation.unsafe_ptr() leads to a pointer to uninitialized memory, and Pointer(to=val) leads to a pointer to initialized memory. unsafe_write() moves a pointer from uninitialized to initialized memory, while unsafe_take_pointee() and unsafe_deinit_pointee() move it back. A pointer to initialized memory can be read or mutated in place. Calling dealloc() on the Allocation leaves a dangling pointer.](../images/pointers/pointer-lifecycle-dark.png#dark)
Figure 2. Lifecycle of a Pointer
### Allocating memory Use the [`std.memory.alloc`](/docs/std/memory/alloc/) module to allocate and deallocate memory. To allocate memory, you need to provide a *layout*, which specifies: - The type of value to be stored (for example, `Int`). - The number of values to allocate space for. - Optionally, the memory alignment for the allocation. The `alloc()` function returns an `Allocation`, an explicitly-destroyed handle that holds an unsafe pointer to the allocated memory and the layout used to allocate it. Use `dealloc()` to free the allocation and its associated memory. ```mojo from std.memory.alloc import alloc, dealloc, Layout var allocation = alloc(Layout[Int](count=4)) # Use allocation var ptr = allocation.unsafe_ptr() for i in range(4): ptr.unsafe_offset(i).unsafe_write(i) # Release allocation dealloc(allocation^) ``` You can also write the allocation above as `alloc[Int]({count = 4})`. Because `Allocation` is an explicitly-destroyed type, you must deallocate it before it goes out of scope. Allocation failure terminates the program; you can't catch this failure with a `try/except` block. The `alloc()` function always returns an allocation with a valid, non-null pointer pointing to the allocated space. The allocated space is *uninitialized*—like a variable that's been declared but not initialized. ### Initializing the pointee To initialize allocated memory, `Pointer` provides the [`unsafe_write()`](/docs/std/memory/pointer/Pointer/#unsafe_write) method, which moves a value into the pointer's memory location: ```mojo no-test str_ptr.unsafe_write(my_string^) ``` Note that to move the value, you usually need to add the transfer sigil (`^`), unless the value is an [implicitly copyable](/docs/std/traits/copyable/ImplicitlyCopyable/) type (like `Int`) or a newly-constructed, "owned" value: ```mojo str_ptr.unsafe_write("Owned string") ``` To copy a value into the pointer's memory location instead of moving it, pass it as the `copy` keyword argument: ```mojo no-test ptr.unsafe_write(copy=my_value) ``` Alternately, you can get a pointer to an existing value by calling the `Pointer` initializer with the keyword `to` argument. This is useful for getting a pointer to a value on the stack, for example. ```mojo var counter: Int = 5 var ptr = Pointer(to=counter) ``` Note that when calling `Pointer(to=value)`, you don't need to allocate memory, since you're pointing to an existing value. ### Dereferencing pointers Use the `[]` dereference operator to access the value stored at a pointer (the "pointee"). ```mojo # Read from pointee print(ptr[]) # Mutate pointee ptr[] = 0 ``` ```output 5 ``` If you've allocated space for multiple values, you can use subscript syntax with the `unsafe_offset` keyword argument to access the values: ```mojo no-test ptr[unsafe_offset=3] = 0 # Equivalent to: ptr.unsafe_offset(3)[] = 0 ``` You cannot safely use the dereference operator on uninitialized memory, even to *initialize* a pointee. This is because assigning to a dereferenced pointer calls lifecycle methods on the existing pointee (such as the deinitializer, move initializer or copy initializer). ```mojo var allocation = alloc[String]({count = 1}) var str_ptr = allocation.unsafe_ptr() # str_ptr[] = "Testing" # Undefined behavior! str_ptr.unsafe_write("Testing") str_ptr[] += " pointers" # Works now ``` ### Destroying or removing values The [`unsafe_take_pointee()`](/docs/std/memory/pointer/Pointer/#unsafe_take_pointee) method moves a pointee from the memory location pointed to by `ptr`. This is a consuming move. It invokes the move initializer on the destination value. It leaves the memory location uninitialized. The [`unsafe_deinit_pointee()`](/docs/std/memory/pointer/Pointer/#unsafe_deinit_pointee) method calls the deinitializer on the pointee, and leaves the memory location pointed to by `ptr` uninitialized. Both `unsafe_take_pointee()` and `unsafe_deinit_pointee()` require that the pointer is non-null, and the memory location contains a valid, initialized value of the pointee's type; otherwise the function results in undefined behavior. Calling [`unsafe_write_move_from(self, src)`](/docs/std/memory/pointer/Pointer/#unsafe_write_move_from) moves the value pointed to by `src` into the memory location pointed to by `self`. After this operation, ownership of that value transfers from `src` to `self` and the memory at `src` is uninitialized: do not read from it, and do not invoke deinitializers on it. To make the memory valid again, initialize it with a new value using one of the `unsafe_write*()` operations. :::note Mojo assumes the destination memory is uninitialized. It does not destroy existing contents before writing the value from `src`. ::: ### Freeing memory Calling [`dealloc()`](/docs/std/memory/alloc/dealloc/) on an allocation frees the allocated memory. It doesn't call the deinitializers on any values stored in the memory. You need to do that explicitly (for example, using [`unsafe_deinit_pointee()`](/docs/std/memory/pointer/Pointer/#unsafe_deinit_pointee) or one of the other functions described in [Destroying or removing values](#destroying-or-removing-values)). Disposing of a pointer without freeing the associated memory can result in a memory leak—where your program keeps taking more and more memory, because not all allocated memory is being freed. Since deallocating an `Allocation` or `ThinAllocation` consumes the allocation, you're protected from freeing an allocation twice, unless you use the `unsafe_leak()` method described in [Allocations and raising functions](#allocations-and-raising-functions). After freeing a pointer's memory, you're left with a dangling pointer—its address still points to the freed memory. Any attempt to access the memory, like dereferencing the pointer, results in undefined behavior. ## Storing multiple values As mentioned in [Allocating memory](#allocating-memory), you can use a `Pointer` to allocate memory for multiple values. The memory is allocated as a single, contiguous block. The [`unsafe_offset()`](/docs/std/memory/pointer/Pointer/#unsafe_offset) method returns a new pointer offset by the specified number of values from the original pointer: ```mojo var third_ptr = first_ptr.unsafe_offset(2) ``` The offset can also be negative, to move backward through the block. Because `unsafe_offset()` returns a new pointer instead of modifying the original, you assign the result back to a variable to advance it: ```mojo # Advance the pointer one element: ptr = ptr.unsafe_offset(1) ```
![Four consecutive memory locations at addresses 0x..101 through 0x..104, holding the characters M, o, j, and o. first_ptr points to the first location, and first_ptr.unsafe_offset(2) points to the third.](../images/pointers/pointer-offset.png#light) ![Four consecutive memory locations at addresses 0x..101 through 0x..104, holding the characters M, o, j, and o. first_ptr points to the first location, and first_ptr.unsafe_offset(2) points to the third.](../images/pointers/pointer-offset-dark.png#dark)
Figure 3. Pointer offsets
For example, the following code allocates memory to store 6 `Float64` values, and initializes them all to zero. ```mojo var allocation = alloc(Layout[Float64](count=6)) var float_ptr = allocation.unsafe_ptr() for offset in range(6): float_ptr.unsafe_offset(offset).unsafe_write(0.0) ``` Once the values are initialized, you can access them using subscript syntax with the `unsafe_offset` keyword argument: ```mojo float_ptr[unsafe_offset=2] = 3.0 for offset in range(6): print(float_ptr[unsafe_offset=offset], end=", ") ``` ```output 0.0, 0.0, 3.0, 0.0, 0.0, 0.0, ``` ## Pointers and origins The `Pointer` struct has an `origin` parameter to track the origin of the memory it points to. The full parameter signature for `Pointer` looks like this: ```mojo no-test struct Pointer[ mut: Bool, //, T: AnyType, origin: Origin[mut=mut], *, address_space: AddressSpace = .GENERIC, ] ``` For pointers initialized with the `to` keyword argument, the origin is inferred from the origin of the pointee. For example, in the following code, `s_ptr.origin` is the same as the origin of `s`: ```mojo var s = "Testing" var s_ptr = Pointer(to=s) ``` When allocating memory with the `alloc()` function, the returned pointer has an `origin` value of `MutUntrackedOrigin`. This value represents an origin that is mutable and doesn't *alias* existing values. For example, it doesn't point to the memory allocated for any other variable. This memory isn't tracked by Mojo's lifetime checker and you're responsible for freeing it. If you're using a pointer in the implementation of a struct, you usually don't have to worry about the origin, as long as the pointer isn't exposed outside of the struct. For example, if you implement a static array type that allocates memory in its initializer, deallocates in its deinitializer, and doesn't expose the pointer outside of the struct, the default origin is fine. But if the struct exposes a pointer or reference to that memory, you need to set the origin appropriately. For example, the [`List`](/docs/std/collections/list/List/) type has an `unsafe_ptr()` method that returns a `Pointer` to the underlying storage. In this case, the returned pointer should share the origin of the list, since the list is the logical owner of the storage. That method looks something like this: ```mojo no-test def unsafe_ptr[ origin: Origin, address_space: AddressSpace, // ](ref[origin, address_space] self) -> Pointer[ Self.T, origin, address_space=address_space ]: return ( self._data.unsafe_mut_cast[origin.mut]() .unsafe_origin_cast[origin]() .unsafe_address_space_cast[address_space]() ) ``` This returns a copy of the original pointer, with the origin set to match the origin and mutability of the `self` value. A method like this is unsafe, but setting the correct origin makes it safer, since the compiler knows that the pointer is referring to data owned by the list. When taking a pointer as a function argument, you often want to require either a mutable or immutable origin, but otherwise allow the compiler to infer the origin. Here's an example: ```mojo def print_bytes(bytes: Pointer[mut=False, Byte, _], count: Int): for i in range(count): print(hex(bytes[unsafe_offset=i]), end=" ") print() ``` By binding the infer-only `mut` parameter to `False`, and leaving the origin unbound (using `_`), this signature lets the compiler infer the origin, but forces the origin to be immutable. Mojo can implicitly cast a mutable pointer to an immutable pointer, so you can pass a mutable pointer into `print_bytes()`, but the function can't mutate the data. ## Working with nullability `Pointer` is a non-nullable type. To model a null pointer, wrap it in [`Optional`](/docs/std/collections/optional/Optional/): ```mojo var ptr = Optional[Pointer[Int, MutUntrackedOrigin]]() ``` This creates an `Optional` with a value of `None`, which is equivalent to a null pointer. `Optional[Pointer]` has the same memory layout as a raw pointer, so you can pass it across FFI boundaries as `NULL`. To check whether an optional pointer is null, use `Optional` methods: ```mojo if ptr: # ptr is not None — safe to unwrap var p = ptr.value() ``` When you need a non-null value for deferred initialization, use `unsafe_dangling()` instead of an `Optional`: ```mojo var ptr = Pointer[Int, MutUntrackedOrigin].unsafe_dangling() ``` For a practical example of optional pointers in a data structure, see [Self-referential structs](/docs/manual/structs/reference/). ## More memory allocation patterns In some cases, you may not want to hold on to an `Allocation`: - When allocating data for a struct, you may want to use a [`ThinAllocation`](/docs/std/memory/alloc/ThinAllocation/) instead, to avoid using extra memory. - When working with raising functions, you sometimes need to avoid an explicitly-destroyed type like `Allocation` or `ThinAllocation`. The following sections describe these special cases. ### Holding an allocation in a struct field When storing an allocation as a struct field, you may not want to store the extra layout data included in the `Allocation` struct. The layout data is two `Int` values (alignment and element count), typically an extra 16 bytes per allocation. If your struct already tracks the amount of space it's allocated, you can eliminate this extra space by storing a `ThinAllocation`, which is an explicitly-destroyed wrapper around a pointer. The `Allocation.into_thin()` method consumes the original allocation and returns a `ThinAllocation`: ```mojo struct Counter: comptime _layout = Layout[Int].single() var _alloc: ThinAllocation[Int] def __init__(out self, value: Int): self._alloc = alloc(Self._layout).into_thin() self._alloc.unsafe_ptr().unsafe_write(value) def increment(mut self): self._alloc.unsafe_ptr()[] += 1 def get(self) -> Int: return self._alloc.unsafe_ptr()[] def __deinit__(deinit self): # Convert ThinAllocation back into Allocation dealloc(self._alloc^.unsafe_with_layout(Self._layout)) ``` To deallocate a `ThinAllocation`, you need to supply the original layout to reconstruct an `Allocation` using the `unsafe_with_layout()` method. This example shows storing the layout as a comptime member; for a struct with a dynamic size, you can reconstruct the original layout: ```mojo no-test self._alloc^.unsafe_with_layout({count = size}) ``` ### Allocations and raising functions Because `Allocation` and `ThinAllocation` need to be explicitly deallocated before they go out of scope, they can conflict with raising functions. Consider the following code: ```mojo no-test def allocating_function() raises: var data = alloc[Float64]({count = 64}) # ... raising_function(data.unsafe_ptr()) dealloc(data^) ``` Because an error can cause `allocating_function()` to exit without executing the `dealloc()` call, the compiler identifies this as a potential memory leak. There are a couple of approaches to this problem. The function can use a `try`/`except` statement to ensure that the memory is deallocated in the event of an error: ```mojo def allocating_function() raises: var data = alloc[Float64]({count = 64}) # ... try: raising_function(data.unsafe_ptr()) except e: dealloc(data^) raise e^ # propagate the error dealloc(data^) ``` Where this isn't viable, the alternative is to use the `unsafe_leak()` method to take ownership of the allocation's pointer. This consumes the allocation, but requires you to ensure the memory is deallocated. You should consider this pattern a last resort if other patterns don't work: ```mojo def leaky_function() raises: var data_ptr = alloc[Float64]({count = 64}).unsafe_leak() # ... raising_function(data_ptr) dealloc( ThinAllocation(unsafe_owned_ptr=data_ptr).unsafe_with_layout( {count = 64} ) ) ``` Downsides of this approach include: - If `raising_function()` raises an error in this example, `dealloc()` never gets called, leaking the memory. - When you reconstruct an `Allocation` from a `Pointer` like this, you run the risk of freeing the same memory twice. ## Working with foreign pointers When exchanging data with other programming languages, you may need to construct a `Pointer` from a foreign pointer. Mojo restricts creating `Pointer` instances from arbitrary addresses, to avoid users accidentally creating pointers that *alias* each other (that is, two pointers that refer to the same location). However, there are specific methods you can use to get a `Pointer` from a Python or C/C++ pointer. When dealing with memory allocated elsewhere, you need to be aware of who's responsible for freeing the memory. Freeing memory allocated elsewhere can result in undefined behavior. When working with some foreign functions, you may need to supply a pointer with no specific type (a type-erased pointer, or "void pointer" in C/C++). This is equivalent to a Mojo `OpaquePointer`. You also need to be aware of the format of the data stored in memory, including data types and byte order. For more information, see [Converting data: bitcasting and byte order](#converting-data-bitcasting-and-byte-order). ### Creating a Mojo pointer from a raw memory address You can create a `Pointer` from a raw memory address using the `unsafe_from_address` initializer. ```mojo def write_to_address(mmio_address: Int, value: Int32): var ptr = Pointer[Int32, MutUntrackedOrigin]( unsafe_from_address=mmio_address ) # Writing to a raw memory address may require a volatile load/store as the # operation may have side effects not visible to the compiler. # You can specify this using the `volatile` parameter. ptr.unsafe_store[volatile=True](value) ``` This is unsafe, as the caller must ensure the address is valid before writing to it, and that the memory is initialized before reading from it. The caller must also ensure the pointer's origin and mutability are valid for the address; failure to do so may result in undefined behavior. ### Creating a Mojo pointer from a Python pointer The `PythonObject` type defines an [`unsafe_get_as_pointer()`](/docs/std/python/python_object/PythonObject/#unsafe_get_as_pointer) method to construct a `Pointer` from a Python address. :::note Where possible, use safer methods to exchange data with Python, such as the [`python.numpy`](/docs/std/python/numpy/) module, which provides convenience functions for transferring 1D NumPy arrays between Mojo and Python. ::: The following code creates a NumPy array and then accesses the data using a Mojo pointer: ```mojo from std.python import Python def share_array() raises: var np = Python.import_module("numpy") var arr = np.array(Python.list(1, 2, 3, 4, 5, 6, 7, 8, 9)) var ptr = arr.ctypes.data.unsafe_get_as_pointer[.int64]() for i in range(9): print(ptr[unsafe_offset=i], end=", ") print() def main() raises: share_array() ``` ```output 1, 2, 3, 4, 5, 6, 7, 8, 9, ``` This example uses the NumPy [`ndarray.ctypes`](https://numpy.org/doc/stable/reference/generated/numpy.ndarray.ctypes.html#numpy.ndarray.ctypes) attribute to access the raw pointer to the underlying storage (`ndarray.ctypes.data`). The `unsafe_get_as_pointer()` method constructs a `Pointer` to this address. ### Working with C/C++ pointers If you call a C/C++ function that returns a pointer using the [`external_call`](/docs/std/ffi/external_call/) function, you can specify the return type as a `Pointer`, and Mojo will handle the type conversion for you. Notably, the `origin` parameter when working across FFI boundaries should often be set to `(Mut/Immut)UntrackedOrigin`, since the pointer points to memory allocated outside of the Mojo program. ```mojo no-test from std.ffi import external_call def get_foreign_pointer() -> Pointer[Int, MutUntrackedOrigin]: var ptr = external_call[ "my_c_function", # external function name Pointer[Int, MutUntrackedOrigin] # return type ]() return ptr ``` ### Opaque pointers The `OpaquePointer` type is a pointer that does not have a specific type. In other languages, this is usually called a type-erased pointer or a void pointer. Opaque pointers are usually used when interfacing with non-Mojo code, such as a C library function that takes a void pointer. `OpaquePointer` is actually a type alias for `Pointer[NoneType]`, so it has the same API as any other `Pointer`. You can't dereference an opaque pointer, but you can cast it to a specific type using the `unsafe_bitcast()` method. Similarly, you can create an opaque pointer from an existing pointer by bitcasting to `NoneType`. For example: ```mojo var str = "Hello, world!" var str_ptr = Pointer(to=str) var opaque_ptr = str_ptr.unsafe_bitcast[NoneType]() # ... call some foreign function that takes a void pointer ``` ## Converting data: bitcasting and byte order Bitcasting a pointer returns a new pointer that has the same memory location, but a new data type. This can be useful if you need to access different types of data from a single area of memory. This can happen when you're reading binary files, like image files, or receiving data over the network. The following sample processes a format that consists of chunks of data, where each chunk contains a variable number of 32-bit integers. Each chunk begins with an 8-bit integer that identifies the number of values in the chunk. ```mojo def read_chunks( var ptr: Pointer[mut=False, UInt8, _], ) -> List[List[UInt32]]: var chunks = List[List[UInt32]]() # A chunk size of 0 indicates the end of the data var chunk_size = Int(ptr[]) while chunk_size > 0: # Skip the 1 byte chunk_size and get a pointer to the first # UInt32 in the chunk var ui32_ptr = ptr.unsafe_offset(1).unsafe_bitcast[UInt32]() var chunk = List[UInt32](capacity=chunk_size) for i in range(chunk_size): chunk.append(ui32_ptr[unsafe_offset=i]) # List is not implicitly copyable, so it needs the transfer sigil (^) chunks.append(chunk^) # Move our pointer to the next byte after the current chunk ptr = ptr.unsafe_offset(1 + 4 * chunk_size) # Read the size of the next chunk chunk_size = Int(ptr[]) return chunks^ ``` When dealing with data read in from a file or from the network, you may also need to deal with byte order. Most systems use little-endian byte order (also called least-significant byte, or LSB) where the least-significant byte in a multibyte value comes first. For example, the number 1001 can be represented in hexadecimal as 0x03E9, where E9 is the least-significant byte. Represented as a 16-bit little-endian integer, the two bytes are ordered E9 03. As a 32-bit integer, it would be represented as E9 03 00 00. Big-endian or most-significant byte (MSB) ordering is the opposite: in the 32-bit case, 00 00 03 E9. MSB ordering is frequently used in file formats and when transmitting data over the network. You can use the [`byte_swap()`](/docs/std/bit/bit/byte_swap/) function to swap the byte order of a SIMD value from big-endian to little-endian or the reverse. For example, if the function above were reading big-endian data, you'd need to change a single line: ```mojo no-test chunk.append(byte_swap(ui32_ptr[unsafe_offset=i])) ``` ## Working with SIMD vectors The `Pointer` type includes [`unsafe_load()`](/docs/std/memory/pointer/Pointer/#unsafe_load) and [`unsafe_store()`](/docs/std/memory/pointer/Pointer/#unsafe_store) methods for performing aligned loads and stores of scalar values. It also has methods supporting strided load/store and gather/scatter. Strided load loads values from memory into a SIMD vector using an offset (the "stride") between successive memory addresses. This can be useful for extracting rows or columns from tabular data, or for extracting individual values from structured data. For example, consider the data for an RGB image, where each pixel is made up of three 8-bit values, for red, green, and blue. If you want to access just the red values, you can use a strided load or store.
![A row of bytes holding a repeating sequence of R, G, and B pixel values. A pointer points to the first R, and arrows with a stride of 3 skip over each G and B to reach the next R.](../images/pointers/strided-load-storage.png#light) ![A row of bytes holding a repeating sequence of R, G, and B pixel values. A pointer points to the first R, and arrows with a stride of 3 skip over each G and B to reach the next R.](../images/pointers/strided-load-storage-dark.png#dark)
Figure 4. Strided load
The following function uses the [`unsafe_strided_load()`](/docs/std/memory/pointer/Pointer/#unsafe_strided_load) and [`unsafe_strided_store()`](/docs/std/memory/pointer/Pointer/#unsafe_strided_store) methods to invert the red pixel values in an image, 8 values at a time. (Note that this function only handles images where the number of pixels is evenly divisible by eight.) ```mojo def invert_red_channel(ptr: Pointer[mut=True, UInt8, _], pixel_count: Int): # Number of values loaded or stored at a time comptime simd_width = 8 # Bytes per pixel, which is also the stride size comptime bpp = 3 for i in range(0, pixel_count * bpp, simd_width * bpp): var red_values = ptr.unsafe_offset(i).unsafe_strided_load[ width=simd_width ](bpp) # Invert values and store them in their original locations ptr.unsafe_offset(i).unsafe_strided_store[width=simd_width]( ~red_values, bpp ) ``` The [`unsafe_gather()`](/docs/std/memory/pointer/Pointer/#unsafe_gather) and [`unsafe_scatter()`](/docs/std/memory/pointer/Pointer/#unsafe_scatter) methods let you load or store a set of values that are stored in arbitrary locations. You do this by passing in a SIMD vector of *offsets* to the current pointer. For example, when using `unsafe_gather()`, the nth value in the vector is loaded from (pointer address) + offset[n]. ## Safety To use `Pointer` safely, you need to ensure that the pointer points to a single, initialized value. If the value is logically owned by the pointer, you need to ensure the value's deinitializer is called before deallocating the memory. Using `Pointer(to=value)` and the simple dereference (`ptr[]`) ensures that the pointer is as safe as the value it's pointing to. Using any APIs prefixed with `unsafe_` (or that have keyword arguments prefixed with `unsafe_`) results in a potentially unsafe operation. For example: - If you allocate memory, you need to deallocate the memory. If you use the `unsafe_leak()` method to obtain a pointer from an allocation, the Mojo lifetime checker can't track the memory and won't error on possible leaks. You need to ensure the memory gets deallocated. This is also true if you assume responsibility for an allocation by calling a method like `List.unsafe_take_allocation()`. - If you allocate memory, or take ownership of an allocation from another source, you need to track whether pointees are initialized or uninitialized. Accessing uninitialized memory results in undefined behavior. - When accessing more than one value through a pointer (for example, using `unsafe_offset()` or `unsafe_load()`), you're always in unsafe territory. You must track the size of the allocation (to know whether a given address is valid) and which values are initialized. --- ## Python interoperability Not only does Mojo use a Pythonic syntax, our plan is to provide full compatibility with the Python ecosystem. There are two types of compatibility (or interoperability) that we support: - [Calling Python from Mojo](/docs/manual/python/python-from-mojo/): You can import existing Python modules and use them in a Mojo program. This is 100% compatible because we use the CPython runtime without modification for full compatibility with existing Python libraries. You can construct Python objects and call Python functions directly from Mojo, using the CPython interpreter as a dynamic library (shown as `libpython.dylib` in figure 1). - [Calling Mojo from Python](/docs/manual/python/mojo-from-python/): You can extend your Python code with high-performance Mojo code (or incrementally migrate Python code to Mojo). Because Mojo is a compiled language, we can't directly "evaluate" Mojo code from Python. Instead, you must declare which Mojo functions and types are available to be called from Python (declare the "bindings"), and then you can import them in your Python code (shown as `mojo_module` in figure 1) just like any other module—there's no extra compilation step.
Figure 1. A simplified look at how a Mojo program calls into Python and a Python program calls into a Mojo module.
By embracing both directions of language interoperability, you can choose how to use Mojo with Python in a way that works best for your use case. :::note Python requirement Mojo itself doesn't require Python. To use the Mojo ↔ Python interoperability features described in this section, you need Python 3.10–3.14. ::: **To learn more about bridging Python ↔ Mojo, continue reading**: --- ## Calling Mojo from Python If you have an existing Python project that would benefit from Mojo's high-performance computing, you shouldn't have to rewrite the whole thing in Mojo. Instead, you can write just the performance-critical parts your code in Mojo and then call it from Python. :::experiment Beta feature Calling Mojo code from Python is in early development. You should expect a lot of changes to the API and ergonomics. Likewise, this documentation is still a work in progress. See below for [known limitations](#known-limitations). ::: ## Import a Mojo module in Python To illustrate what calling Mojo from Python looks like, we'll start with a simple example, and then dig into the details of how it works and what is possible today. Consider a project with the following structure: ```text project ├── 🐍 main.py └── 🔥 mojo_module.mojo ``` The main entrypoint is a Python program called `main.py`, and the Mojo code includes functions to call from Python. For example, let's say we want a Mojo function to take a Python value as an argument: ```mojo title="mojo_module.mojo" def factorial(py_obj: PythonObject) raises -> Python var n = Int(py=py_obj) return math.factorial(n) ``` And we want to call it from Python like this: ```python title="main.py" import mojo_module print(mojo_module.factorial(5)) ``` However, before we can call the Mojo function from Python, we must declare it so Python knows it exists. Because Python is trying to load `mojo_module`, it looks for a function called `PyInit_mojo_module()`. (If our file was called `foo.mojo`, the function Python looked for would be `PyInit_foo()`.) Within the `PyInit_mojo_module()`, we must declare all Mojo functions and types that are callable from Python using [`PythonModuleBuilder`](/docs/std/python/bindings/PythonModuleBuilder/). So the complete Mojo code looks like this: ```mojo title="mojo_module.mojo" from std.python import PythonObject from std.python.bindings import PythonModuleBuilder from std import math from std.os import abort @export def PyInit_mojo_module() abi("C") -> PythonObject: try: var m = PythonModuleBuilder("mojo_module") m.def_function[factorial]("factorial", docstring="Compute n!") return m.finalize() except e: abort(String("error creating Python Mojo module:", e)) def factorial(py_obj: PythonObject) raises -> PythonObject: # Raises an exception if `py_obj` is not convertible to a Mojo `Int`. var n = Int(py=py_obj) return math.factorial(n) ``` On the Python side, we add the directory containing `mojo_module.mojo` to the Python path, and then use a normal `import` statement to load our Mojo code: ```python title="main.py" import mojo.importer import mojo_module print(mojo_module.factorial(5)) ``` That's it! Try it: ```sh python main.py ``` ```output 120 ``` ### How it works Python supports a standard mechanism called [Python extension modules](https://docs.python.org/3/extending/extending.html) that enables compiled languages (like Mojo, C, C++, or Rust) to make themselves callable from Python in an intuitive way. Concretely, a Python extension module is simply a dynamic library that defines a suitable `PyInit_*()` function. Mojo comes with built-in functionality for defining Python extension modules. The special stuff happens in the `mojo.importer` module we imported. If we have a look at the filesystem after Python imports the Mojo code, we'll notice there's a new `__mojocache__` directory, with a dynamic library (`.so`) file inside: ```text project ├── main.py ├── mojo_module.mojo └── __mojocache__ └── mojo_module.hash-ABC123.so ``` Loading `mojo.importer` loads our Python Mojo [import hook](https://docs.python.org/3/reference/import.html#import-hooks), which behind the scenes looks for a `.mojo` file that matches the imported module name, and if found, compiles it using [`mojo build --emit shared-lib`](/docs/cli/build/#--emit-file_type) to generate a dynamic library. The resulting file is stored in `__mojocache__`, and is rebuilt only when it becomes stale (typically, when the Mojo source file changes). :::note Clearing cached build artifacts The `__mojocache__` directory should contain only derived artifacts. It is always safe to delete the contents of a `__mojocache__` directory. Needed artifacts will simply be rebuilt the next time the Mojo module is imported. ::: ### The `abi` of exported functions An [`@export`](/docs/reference/decorators/export/) function must declare which calling convention it uses with an explicit [`abi`](/docs/reference/function-declarations#abi-c) effect. In a Python extension module, the only function you need to export is the `PyInit_` entry point, and it must use `abi("C")`: ```mojo @export def PyInit_mojo_module() abi("C") -> PythonObject: ... ``` This is because the CPython runtime locates and calls `PyInit_` directly across the C boundary, so it must expose the C calling convention. A `abi("C")` function can't be marked `raises`, which is why the examples above catch any error inside the body and `abort` instead of propagating it. The functions, methods, and initializers you register with the module builder (`def_function`, `def_method`, `def_py_init`, and so on) don't need `@export` at all; you pass them by reference, and Mojo generates the C wrapper that CPython actually calls. That wrapper invokes your function using the Mojo calling convention and translates any raised error into a Python exception, so a registered function such as `factorial` above can freely be marked `raises`. Now that we've looked at the basics of how Mojo can be used from Python, let's dig into the available features and how you can leverage them to accelerate your Python with Mojo. ## Bindings features ### Binding Mojo types You can bind any Mojo type for use in Python using [`PythonModuleBuilder`](/docs/std/python/bindings/PythonModuleBuilder/). For example: ```mojo @fieldwise_init struct Person(Movable, Writable): var name: String var age: Int @export def PyInit_person_module() abi("C") -> PythonObject: try: var mb = PythonModuleBuilder("person_module") var person_type = mb.add_type[Person]("Person") except e: abort("error creating Mojo module") ``` When you call [`add_type()`](/docs/std/python/bindings/PythonModuleBuilder/#add_type), it returns a [`PythonTypeBuilder`](/docs/std/python/bindings/PythonTypeBuilder/), which you can then use to bind the type constructor (see [binding Python initializers](#constructing-mojo-objects-in-python), below) and methods. Any Mojo type bound using a `PythonTypeBuilder` has the resulting Python 'type' object globally registered, enabling two features: - Constructing Python objects that wrap Mojo values for use from Python using `PythonObject(alloc=Person(..))`. - Downcasting using `python_obj.downcast_value_ptr[Person]()` :::note Mojo types must implement [`Writable`](/docs/std/format/Writable/) to be bound for use in Python. Additional traits are required for specific binding features: `Movable` for custom initializers (`def_py_init`), and both `Defaultable` and `Movable` for default initializers (`def_init_defaultable`). ::: However, merely binding a Mojo type to a Python `type` object isn't very useful on its own. Next, we'll tell Python how to interact with our Mojo type—starting with how to construct instances of our Mojo type from within Python. ### Constructing Mojo objects in Python Mojo types can be constructed from Python by declaring a Mojo initializer function as a Python-compatible object initializer using [`def_py_init()`](/docs/std/python/bindings/PythonTypeBuilder/#def_py_init) when you add the type to your module. For example: ```mojo @export def PyInit_person_module() abi("C") -> PythonObject: try: var mb = PythonModuleBuilder("person_module") # highlight-start _ = mb.add_type[Person]("Person").def_py_init[Person.py_init]() # highlight-end return mb.finalize() except e: abort(String("error creating Python Mojo module:", e)) @fieldwise_init struct Person(Movable, Writable): var name: String var age: Int # highlight-start @staticmethod def py_init( out self: Person, args: PythonObject, kwargs: PythonObject ) raises: # Validate argument count if len(args) != 2: raise Error("Person() takes exactly 2 arguments") # Convert Python arguments to Mojo types var name = String(args[0]) var age = Int(args[1]) self = Self(name, age) # highlight-end ``` With this Mojo binding, you can create `Person` instances in Python: ```python person = person_module.Person("Sarah", 32) print(person) ``` ```output Person(name=Sarah, age=32) ``` For types that support default construction, you can use the simpler [`def_init_defaultable()`](/docs/std/python/bindings/PythonTypeBuilder/#def_init_defaultable) method: ```mojo var counter_type = m.add_type[Counter]("Counter") counter_type.def_init_defaultable[Counter]() ``` This enables Python code to create instances without arguments: ```python counter = counter_module.Counter() # Creates Counter() ``` :::note "Constructor" vs "Initializer" In Python, object construction happens across both the `__new__()` and `__init__()` methods, so the `__init__()` method is technically just the attribute initializer. However, in a Mojo struct, there's no `__new__()` method, so we prefer to always call `__init__()` the initializer. ::: ### Returning Mojo objects to Python Mojo functions called from Python don't just need to be able to accept [`PythonObject`](/docs/std/python/python_object/PythonObject/) values as arguments, they also need to be able to return new values. And sometimes, they even need to be able to return Mojo native values back to Python. This is possible by using the `PythonObject(alloc=)` constructor. An example of this looks like: ```mojo def create_person() -> PythonObject: var person = Person("Sarah", 32) return PythonObject(alloc=person^) ``` :::caution `PythonObject(alloc=...)` will raise an exception if the provided Mojo object type had not previously been registered using [`PythonModuleBuilder.add_type()`](/docs/std/python/bindings/PythonModuleBuilder/#add_type). ::: ### `PythonObject` to Mojo values Within any Mojo code that is handling a [`PythonObject`](/docs/std/python/python_object/PythonObject/), but especially within Mojo functions called from Python, it's common to expect an argument of a particular type. There are two ways in which a `PythonObject` can be turned into a native Mojo value: - **Converting** a Python object into a newly constructed Mojo value that has the same logical value as the original Python object. This is handled by the [`ConvertibleFromPython`][ConvertibleFromPython] trait. - **Downcasting** a Python object that holds a native Mojo value to a pointer to that inner value. This is handled by [`PythonObject.downcast_value_ptr()`][downcast_value_ptr]. #### `PythonObject` conversions Many Mojo types support conversion directly from equivalent Python types, via the [`ConvertibleFromPython`][ConvertibleFromPython] trait: ```mojo # Given a person, clone them and give them a different name. def create_person( name_obj: PythonObject, age_obj: PythonObject ) raises -> PythonObject: # These conversions will raise an exception if they fail var name = String(name_obj) var age = Int(age_obj) return PythonObject(alloc=Person(name, age)) ``` Which could be called from Python using: ```python person = mojo_module.create_person("John Smith") ``` Passing invalid arguments will result in a runtime argument error: ```python person = mojo_module.create_person(42) ``` #### `PythonObject` downcasts Downcasting from `PythonObject` values to the inner Mojo value: ```mojo def print_age(person_obj: PythonObject) raises: # Raises if `obj` does not contain an instance of the Mojo `Person` type. var person = person_obj.downcast_value_ptr[Person]() print("Person is", person[].age, "years old") ``` Unsafe mutation via downcasting is also supported. It is up to the user to ensure that this mutable pointer does not alias any other pointers to the same object within Mojo: ```mojo def birthday(person_obj: PythonObject): var person = person_obj.downcast_value_ptr[Person]() person[].age += 1 ``` Entirely unchecked downcasting—which does no type checking—can be done using: ```mojo def get_person(person_obj: PythonObject): var person = person_obj.unchecked_downcast_value_ptr[Person]() ``` Unchecked downcasting can be used to eliminate overhead when optimizing a tight inner loop with Mojo, and you've benchmarked and measured that type checking downcasts is a significant bottleneck. {/**/} ### Methods When binding Mojo objects for use from Python, you can expose chosen methods to Python as well, using [`PythonTypeBuilder.def_method()`](/docs/std/python/bindings/PythonTypeBuilder/#def_method). Currently, Mojo methods being exposed to Python must be written with a modification compared to normal Mojo methods: they must be a `@staticmethod` that takes either `py_self: PythonObject` or `self_ptr: Pointer[Self]`: ```mojo from std.python import PythonObject from std.python.bindings import PythonModuleBuilder from std.os import abort @export def PyInit_mojo_module() abi("C") -> PythonObject: try: var mb = PythonModuleBuilder("mojo_module") # highlight-start _ = mb.add_type[Person]("Person") .def_method[Person.get_name]("get_name") .def_method[Person.set_age]("set_age") # highlight-end return mb.finalize() except e: abort("error creating Mojo module") struct Person(Writable): var name: String var age: Int # highlight-start @staticmethod def get_name(py_self: PythonObject) raises -> PythonObject: var self_ptr = py_self.downcast_value_ptr[Self]() return self_ptr[].name @staticmethod def set_age( self_ptr: Pointer[mut=True, Self], new_age: PythonObject, ) raises: self_ptr[].age = Int(new_age) # highlight-end def write_to(self, mut writer: Some[Writer]): t"Person({self.name}, {self.age})".write_to(writer) ``` Taking `py_self: PythonObject` allows access to the full `PythonObject` allocation that a Mojo object instance is stored inside of. Typically though, taking `self_ptr: Pointer[Self]` will minimize boilerplate in the common case that a method merely needs to access the fields of an object. Mojo methods called from Python are currently required to take non-standard self types due to limitations that will be lifted in future versions of Python Mojo bindings. ### Static methods Python Mojo bindings supports exposing Python `@staticmethods`, bound using [`PythonTypeBuilder.def_staticmethod()`](/docs/std/python/bindings/PythonTypeBuilder/#def_staticmethod). A function declared using `def_staticmethod()` is callable as a static method on the type within Python, without needing an object instance. ```mojo from std.python import PythonObject from std.python.bindings import PythonModuleBuilder from std.os import abort @export def PyInit_mojo_module() abi("C") -> PythonObject: try: var mb = PythonModuleBuilder("mojo_module") # highlight-start mb.add_type[Person]("Person") .def_staticmethod[Person.is_valid_age]("is_valid_age") # highlight-end return mb.finalize() except e: abort("error creating Mojo module") struct Person(Writable): var name: String var age: Int # highlight-start @staticmethod def is_valid_age(age_obj: PythonObject) raises -> PythonObject: var age = Int(age_obj) return 0 <= age <= 130 # highlight-end def write_to(self, mut writer: Some[Writer]): t"Person({self.name}, {self.age})".write_to(writer) ``` Calling a Mojo function bound as a static method looks like a typical Python static method call directly on the type object: ```python title="main.py" from mojo_module import Person print(Person.is_valid_age(45)) # Prints 'True' print(Person.is_valid_age(-1)) # Prints 'False' ``` ### Keyword arguments [Keyword arguments in Mojo](/docs/manual/functions/#keyword-arguments) come in two forms: 1. Keyword-only arguments: `def foo(*, x: Int)` This is not currently supported in Python Mojo bindings. 2. [Variadic keyword arguments](/docs/manual/functions/#variadic-keyword-arguments): `def foo(var **kwargs: Int)` This is supported in Python Mojo bindings when used in the unsugared form: `def foo(kwargs: StringDict)`. (The `**kwargs` syntax limitation will be removed in the future.) You can define Mojo functions that accept variadic keyword arguments using [`StringDict[PythonObject]`](/docs/std/collections/dict/StringDict/) as the last argument. A simple example looks like: ```python import mojo_module result = mojo_module.sum_kwargs_ints(a=10, b=20, c=30) # returns 60 ``` ```mojo from std.collections import StringDict def sum_kwargs_ints(kwargs: StringDict[PythonObject]) raises -> PythonObject: var total = 0 for entry in kwargs.items(): total += Int(entry.value) return PythonObject(total) ``` Keyword arguments are also supported following normal positional arguments. Additionally, getting specific keyword arguments is a dictionary lookup on the `StringDict`: ```mojo from std.collections import StringDict def duration_in_seconds( hours_obj: PythonObject, minutes_obj: PythonObject, kwargs: StringDict[PythonObject] ) raises -> PythonObject: var hours = Int(hours_obj) var minutes = Int(minutes_obj) var seconds = Int(kwargs["seconds"]) return hours * 3600 + minutes * 60 + seconds ``` In this example, if a call to `duration_in_seconds()` is missing the required `"seconds"` named argument, a runtime exception will occur: ```python title="main.py" from mojo_module import duration_in_seconds # Pass hours and minutes, missing "seconds" duration_in_seconds(4, 5) # ERROR: KeyError ``` Keyword arguments are supported when bindings top-level functions, methods, and static methods. ### Variadic arguments Python and Mojo variadic arguments are normally written using the following syntax: ```mojo def foo(*args: Int): ... ``` However, this syntax is not yet supported in Python/Mojo bindings, because functions bound using [`def_function()`](/docs/std/python/bindings/PythonModuleBuilder/#def_function) support only fixed-arity functions. As a workaround, you can expose Mojo functions that accept a variadic number of arguments to Python using the lower-level [`def_py_function()`](/docs/std/python/bindings/PythonModuleBuilder/#def_py_function) interface, which leaves it to the user to validate the number of arguments provided: ```mojo @export def PyInit_mojo_module() abi("C") -> PythonObject: try: var b = PythonModuleBuilder("mojo_module") b.def_py_function[count_args]("count_args") b.def_py_function[sum_args]("sum_args") b.def_py_function[lookup]("lookup") def count_args(py_self: PythonObject, args_tuple: PythonObject) raises: return len(args_tuple) def sum_args(py_self: PythonObject, args_tuple: PythonObject) raises: var total = args_tuple[0] for i in range(1, len(args_tuple)): total += args_tuple[i] return total def lookup(py_self: PythonObject, args_tuple: PythonObject) raises: if len(args_tuple) != 2 and len(args_tuple) != 3: raise Error("lookup() expects 2 or 3 arguments") var collection = args_tuple[0] var key = args_tuple[1] try: return collection[key] except e: if len(args) == 3: return args_tuple[2] else: raise e ``` ## Strategies for porting Python to Mojo ### Writing Pythonic code in Mojo In this approach to bindings, we embrace the flexibility of Python, and eschew trying to convert `PythonObject` arguments into the narrowly constrained, strongly-typed space of the Mojo type system, in favor of just writing some code and letting it raise an exception at runtime if we got something wrong. The flexibility of `PythonObject` enables a unique programming style, wherein Python code can be "ported" to Mojo with relatively few changes. ```python def foo(x, y, z): x[y] = int(z) x = y + z ``` Rule of thumb: Any Python builtin function should be accessible in Mojo using `Python.()`. ```mojo def foo(x: PythonObject, y: PythonObject, z: PythonObject) -> PythonObject: x[y] = Python.int(z) x = y + z ``` ## Building Mojo extension modules You can create and distribute your Mojo modules for Python in the following ways: - As source files, compiled on demand using the Python Mojo importer hook. The advantage of this approach is that it's easy to get started with, and keeps your project structure simple, while ensuring that your imported Mojo code is always up to date after you make an edit. - As pre-built Python extension module `.so` dynamic libraries, compiled using: ```bash mojo build mojo_module.mojo --emit shared-lib -o mojo_module.so ``` This has the advantage that you can specify any other necessary build options manually (optimization or debug flags, import paths, etc.), providing an "escape hatch" from the Mojo import hook abstraction for advanced users. ## Known limitations While we have big ambitions for Python to Mojo interoperability—our goal is for Mojo to be the best way to extend Python—this feature is still in early and active development, and there are some limitations to be aware of. These will be lifted over time. - **Keyword arguments syntax.** Currently, Mojo functions called from Python only accept keyword arguments when using a trailing `kwargs: StringDict[PythonObject]` argument. Support for native `**kwargs` syntax will be added in the future. - **Mojo package dependencies.** Mojo code that has dependencies on packages other than the Mojo stdlib (like those in the ever-growing [Modular Community](https://github.com/modular/modular-community) package channel) are currently only supported when building Mojo extension modules manually, as the Mojo import hook does not currently support a way to specify import paths for Mojo package dependencies. - **Properties.** Computed properties getter and setters are not currently supported. - **Expected type conversions.** A handful of Mojo standard library types can be constructed directly from equivalent Python builtin object types, by implementing the [`ConvertibleFromPython`][ConvertibleFromPython] trait. However, many Mojo standard library types do not yet implement this trait, so may require manual conversion logic if needed. [ConvertibleFromPython]: /docs/std/python/conversions/ConvertibleFromPython/ [downcast_value_ptr]: /docs/std/python/python_object/PythonObject#downcast_value_ptr --- ## Calling Python from Mojo The Python ecosystem is full of useful libraries, so you shouldn't have to rewrite them in Mojo. Instead, you can simply import Python packages and call Python APIs from Mojo. The Python code runs in a standard Python interpreter (CPython), so your existing Python code doesn't need to change. ## Specify your Python version Mojo doesn't include a CPython interpreter—it uses the CPython interpreter provided by your environment's default Python version. So be sure you know which Python version you're using in each environment where your Mojo code will run. To ensure you get consistent results, we recommend you [use Pixi](https://pixi.prefix.dev/latest/installation/) to manage your package dependency and virtual environment. In a Pixi project, you can specify the Python version like this: ```sh pixi add "python==3.11" ``` Now, even if your operating system's default Python version is something else, your Pixi project (and the Mojo code inside) always uses Python 3.11. ```sh pixi run python --version ``` ```output Python 3.11.0 ``` ## Import a Python module in Mojo To import a Python module in Mojo, just call [`Python.import_module()`](/docs/std/python/python/Python/#import_module) with the module name. The following shows an example of importing the standard Python [NumPy](https://numpy.org/) package: ```mojo from std.python import Python def main() raises: # This is equivalent to Python's `import numpy as np` var np = Python.import_module("numpy") # Now use numpy as if writing in Python var array = np.array(Python.list(1, 2, 3)) print(array) # [1 2 3] ``` Assuming that you have the NumPy package installed in your environment, this imports NumPy and you can use any of its features. If you want to use Python builtin APIs, you just need to import the `builtins` module the same way. For example: ```mojo from std.python import Python def main() raises: var np = Python.import_module("numpy") var array = np.array(Python.list(1, 2, 3)) var builtins = Python.import_module("builtins") print(builtins.type(array)) # ``` A few things to note: - The `import_module()` method returns a reference to the module in the form of a [`PythonObject`](/docs/std/python/python_object/PythonObject/) wrapper. You must store the reference in a variable and then use it as shown in the example above to access functions, classes, and other objects defined by the module. See [Mojo wrapper objects](/docs/manual/python/types/#mojo-wrapper-objects) for more information about the `PythonObject` type. - Currently, you cannot import individual members (such as a single Python class or function). You must import the whole Python module and then access members through the module name. - Mojo doesn't yet support top-level code, so the `import_module()` call must be inside another method. This means you may need to import a module multiple times or pass around a reference to the module. This works the same way as Python: importing the module multiple times won't run the initialization logic more than once, so you don't pay any performance penalty. - `import_module()` may raise an exception. Raising exceptions is much more common in Python code than in the Mojo standard library, which [limits their use for performance reasons](/docs/roadmap#the-standard-library-has-limited-exceptions-use). - We recommend using a package manager such as pixi, uv, or conda to manage your environment. For instructions on setting up a Mojo project with pixi, see [Create a Mojo project](/docs/manual/get-started/#1-create-a-mojo-project) in the Get started with Mojo tutorial. :::caution [`mojo build`](/docs/cli/build/) doesn't include the Python packages used by your Mojo project. Instead, Mojo loads the Python interpreter and Python packages at runtime, so they must be provided in the environment where you run the Mojo program (such as inside the pixi environment where you built the executable). ::: ### Import a local Python module If you have some local Python code you want to use in Mojo, just add the directory to the Python path and then import the module. For example, suppose you have a Python file named `mypython.py`: ```python title="mypython.py" import numpy as np def gen_random_values(size, base): # generate a size x size array of random numbers between base and base+1 random_array = np.random.rand(size, size) return random_array + base ``` Here's how you can import it and use it in a Mojo file: ```mojo title="main.mojo" from std.python import Python def main() raises: Python.add_to_path("path/to/module") var mypython = Python.import_module("mypython") var values = mypython.gen_random_values(2, 3) print(values) ``` Both absolute and relative paths work with [`add_to_path()`](/docs/std/python/python/Python/#add_to_path). For example, you can import from the local directory like this: ```mojo Python.add_to_path(".") ``` --- ## Python types When calling Python methods, Mojo needs to convert back and forth between native Python objects and native Mojo objects. Most of these conversions happen automatically, but there are a number of cases that Mojo doesn't handle yet. In these cases you may need to do an explicit conversion, or call an extra method. ## Mojo types in Python Mojo primitive types implicitly convert into Python objects. Today we support integers, floats, booleans, and strings. To demonstrate, the following example dynamically creates an in-memory Python module named `py_utils` containing a `type_printer()` function, which simply prints the type of a given value. Then you can see how different Mojo values convert into corresponding Python types. ```mojo from std.python import Python def main() raises: var py_module = """ def type_printer(value): print(type(value)) """ var py_utils = Python.evaluate(py_module, file=True, name="py_utils") py_utils.type_printer(4) py_utils.type_printer(3.14) py_utils.type_printer(True) py_utils.type_printer("Mojo") ``` ```output ``` ## Python types in Mojo You can also create and use Python objects from Mojo. ### Mojo wrapper objects When you use Python objects in your Mojo code, Mojo adds the [`PythonObject`](/docs/std/python/python_object/PythonObject/) wrapper around the Python object. This object exposes a number of common double underscore methods (dunder methods) like `__getitem__()` and `__getattr__()`, passing them through to the underlying Python object. Most of the time, you can treat the wrapped object just like you'd treat it in Python. You can use dot-notation to access attributes and call methods, and use the `[]` operator to access an item in a sequence. You can explicitly create a wrapped Python object by initializing a `PythonObject` with a Mojo integer, float, boolean, or string. Additionally, you can create several types of Python collections directly in Mojo using the [`Python.dict()`](/docs/std/python/python/Python/#dict), [`Python.list()`](/docs/std/python/python/Python/#list), and [`Python.tuple()`](/docs/std/python/python/Python/#tuple) static methods. For example, to create a Python dictionary, use the [`Python.dict()`](/docs/std/python/python/Python/#dict) method: ```mojo from std.python import Python def main() raises: var py_dict = Python.dict() py_dict["item_name"] = "whizbang" py_dict["price"] = 11.75 py_dict["inventory"] = 100 print(py_dict) ``` ```output {'item_name': 'whizbang', 'price': 11.75, 'inventory': 100} ``` With the [`Python.list()`](/docs/std/python/python/Python/#list) method, you can create a Python list and optionally initialize it: ```mojo from std.python import Python def main() raises: var py_list = Python.list("cat", 2, 3.14159, 4) var n = py_list[2] print("n =", n) py_list.append(5) py_list[0] = "aardvark" print(py_list) ``` ```output n = 3.14159 ['aardvark', 2, 3.14159, 4, 5] ``` The [`Python.tuple()`](/docs/std/python/python/Python/#tuple) method creates a Python tuple of values: ```mojo from std.python import Python def main() raises: var py_tuple = Python.tuple("cat", 2, 3.1415, "cat") var n = py_tuple[2] print("n =", n) print("Number of cats:", py_tuple.count("cat")) ``` ```output n = 3.1415 Number of cats: 2 ``` If you want to construct a Python type that doesn't have a literal Mojo equivalent, you can also use the [`Python.evaluate()`](/docs/std/python/python/Python/#evaluate) method. For example, to create a Python `set`: ```mojo from std.python import Python def main() raises: var py_set = Python.evaluate('{2, 3, 2, 7, 11, 3}') var num_items = len(py_set) print(num_items, "items in the set.") var contained = 7 in py_set print("Is 7 in the set:", contained) ``` ```output 4 items in the set. Is 7 in the set: True ``` `PythonObject` implements the [`Writable`](/docs/std/format/Writable/) trait. This allows you to print Python values using the built-in [`print()`](/docs/std/io/io/print/) function, as shown in several of the previous examples. However, most other Mojo APIs don't accept `PythonObject` values directly. In these cases you'll need to explicitly convert a Python value into a native Mojo value. For example: ```mojo from std.python import Python from std.python import PythonObject def main() raises: var py_string = PythonObject("Hello, Mojo!") var py_bool = PythonObject(True) var py_int = PythonObject(123) var py_float = PythonObject(3.14) var mojo_string = String(py=py_string) var mojo_bool = Bool(py=py_bool) var mojo_int = Int(py=py_int) var mojo_float = Float64(py=py_float) ``` ### Comparing Python types in Mojo You can use Python objects in Mojo comparison expressions, and the Mojo `is` operator also works to compare the identity of two Python objects. Python values like `False` and `None` evaluate as false in Mojo boolean expressions as well. If you need to know the type of the underlying Python object, you can use the [`Python.type()`](/docs/std/python/python/Python/#type) method, which is equivalent to the Python `type()` builtin. You can test if a Python object is of a particular type by performing an identity comparison against the type as shown below: ```mojo from std.python import Python def main() raises: var value1 = PythonObject(3.7) var value2 = Python.evaluate("10/3") # Compare values print("Is value1 greater than 3:", value1 > 3) print("Is value1 greater than value2:", value1 > value2) # Compare identities var value3 = value2 print("value1 is value2:", value1 is value2) print("value2 is value3:", value2 is value3) # Compare types var py_float_type = Python.evaluate("float") print("Python float type:", py_float_type) print("value1 type:", Python.type(value1)) print("Is value1 a Python float:", Python.type(value1) is py_float_type) ``` ```output Is value1 greater than 3: True Is value1 greater than value2: True value1 is value2: False value2 is value3: True Python float type: value1 type: Is value1 a Python float: True ``` --- ## Mojo tips for Python devs Mojo is designed with Python programmers in mind, but it isn't "just Python, only faster." Mojo introduces a type system, ownership-aware semantics, and low-level control that, as a Python developer, you may not have had to reason about to make your code work. This guide offers a practical resource for Python developers. It shows how familiar Python patterns translate into Mojo, where your instincts still apply and where Mojo asks you to build a new mental model. This isn't a tutorial. It's a core set of language migration tips and patterns to support you as you migrate to Mojo. ## Mojo's core model for Python developers Mojo looks like Python but its execution model is closer to Rust, Swift, C++, and other systems languages. Key differences from Python include: - **Mojo is statically typed.** In Python, types are optional hints that the interpreter _mostly_ ignores at runtime. In Mojo, types are first-class. The compiler uses them to generate fast, specialized machine code. - **Mojo compiles to machine code.** Python runs through an interpreter that translates your code at runtime, adding overhead to every operation. Mojo compiles directly to native machine code. This gives you fast and predictable performance, with no interpreter overhead. - **Mojo prefers _value semantics_ and explicit mutability.** In Python, most objects are mutable references, so assigning a list doesn't copy it. In Mojo, assigning or passing a value typically creates an independent copy. Changes to one value don't affect others unless you make sharing explicit. This keeps data flow clear and enables safe parallelism. - **Mojo supports modern ownership, but it doesn't trap you in "safe-only" abstractions.** With ownership, the compiler tracks which variables and fields control a value's lifetime. That lets Mojo manage memory effectively, without a garbage collector or reference counting. When you need low-level control, such as interfacing with C-language libraries, you can manage memory explicitly using `alloc()`, `dealloc()` and `Pointer`. - **Mojo brings together ideas from Rust, C++, and Python.** It combines Python's readability with a performance model inspired by systems languages like Rust and C++. Understanding these differences is essential for writing correct, fast Mojo. ## Moving from Python to Mojo This section introduces a curated set of migration topics that explore common Mojo patterns. ### Value semantics In Mojo, when you assign a value to a new variable, it's given a unique owned value, not a second reference pointing to the same data. This can catch new adopters off guard. In Python, both `a` and `b` refer to the same list: ```python a = [1, 2, 3] b = a b.append(4) print(a) # [1, 2, 3, 4] # a changed because b and a both point to the same list ``` In Mojo, assignment gives you a copy. If you're not working with trivial types (like `Int` or `Bool`) or types with built-in copy semantics (like `String`), you may need to use an explicit copy call. You can also use and create types that are implicitly copyable. ```mojo var a = "hello" # hello var b = a # hello, implicit copy b = b + " world" # hello world print(a) # hello print(b) # hello world var c: List[Int] = [1, 2, 3] var d = c.copy() # d is an independent copy d.append(4) # [1, 2, 3, 4] print(c) # [1, 2, 3] c is unchanged ``` Mojo uses `var` to declare variables. A `var` binding owns its value. Using `var` consistently makes your code easier to read. It's clear when you introduce a new binding and when you reassign an existing one. To use Python-like reference behavior, declare `b` with `ref` instead of `var`: ```mojo var a: List[Int] = [1, 2, 3] ref b = a # b is a reference to the same value b.append(4) # The list updates. a still owns the list. print(a) # [1, 2, 3, 4] ``` ### Mutability In both Python and Mojo, almost everything you create can be changed after the fact. In Mojo, there are some special rules. **Python**: Nearly everything is mutable. ```python a = 10 # 10 b = a # 10 b = b + 10 # 20 print(a, b) # 10 20 ``` **Mojo**: All variables are mutable by default. Function arguments aren't mutable by default. ```mojo var x = 10 # 10 x = 20 # 20, with a warning that x's previous assignment # to 10 was never used def foo(value: Int): value += 1 # Error, expression must be mutable var y = 20 foo(y) ``` Default immutability in the function gives the compiler more room to optimize. Using the `mut` keyword in the argument declaration makes it mutable. ```mojo def foo(mut value: Int): value += 1 # This works ``` Explicit mutability makes code easier to reason about because mutability is visible. You can look at a function signature and immediately see which values can change and which can't. ## Numbers Python gives you `int` and `float` with arbitrary precision. Mojo takes a different approach: it uses explicit, fixed-width numeric types so the compiler can optimize aggressively and scale across parallel execution. Mojo provides concrete numeric types like `Int`, `Int8`, `Int16`, `Int32`, `Int64`, `Float16`, `Float32`, and `Float64`. Fixed width isn't a limitation in Mojo, it's a feature that lets the compiler pack numbers with known sizes into memory. ### SIMD types In many languages, SIMD shows up later as a specialized tool for advanced users. In Mojo, SIMD is part of the core compute model. Mojo implements its primitive numeric types as SIMD values under the hood. That lets the compiler operate on multiple values with a single hardware instruction. When you choose the right numeric type, you give the compiler more room to generate faster code. ### Int types **Python**: Python's `int` uses arbitrary precision. It can hold integers as large as memory allows. **Mojo's `Int` type**: The general `Int` type maps to your machine's native word size. This is typically 64 bits on a 64-bit system. You can always check: ```mojo from std.sys import size_of def main(): var a: Int = 5 var bytes = size_of[Int]() print(bytes) # 8 on a 64-bit system ``` ### Floating point types **Python**: In Python, `float` is always 64-bit. **Mojo**: Mojo floating point types _aren't_ arbitrary precision. Mojo doesn't provide a default floating point type, the way it does with integers. That means there's no built-in `Float` type. When you're just starting with Mojo, stick to `Float32` or `Float64` floating point. ### Division and types Mojo division behaves differently than in Python. In Python, dividing two integers always produces a float: ```python a = 7 b = 2 print(7 / 2) # 3.5 — always float ``` In Mojo, if you want integer division to return a floating-point result, you must use explicit casting: ```mojo var a: Int = 7 var b: Int = 2 print(Float64(a) / Float64(b)) # 3.5 — explicit float division ``` ### Mojo division operators In Mojo, `/` returns a value that always matches the type of the operands. A floating point number divided by a floating point number returns a floating point number, and an integer divided by an integer returns an integer: ```mojo var a: Int = 7 var b: Int = 2 print(a / b) # 3, result type matches operand type ``` Python programmers may be a bit surprised that / isn't "true division." It returns a truncated result, but the result is biased towards zero: ```mojo var c = -7 var d = 2 print(c / d) # -3, not -4, truncates towards zero ``` `//` performs floored division, in the direction of negative infinity: ```mojo print(c // d) # -4, not -3, truncates towards negative infinity ``` Like `/`, the type returned by `//` is preserved from the operands: ```mojo var e: Float64 = 7.0 var f: Float64 = 2.0 print(e // f) # 3.0, floors toward negative infinity print(e / f) # 3.5 var g: Float64 = -7.0 print(g // f) # -4.0, floors toward negative infinity print(g / f) # -3.5 ``` ## Data structures: lists **Python**: In Python, lists are dynamic. They can hold mixed types and grow freely. ```python nums = ["one", 2.0, 3] nums.append(4) # ['one', 2.0, 3, 4] ``` **Mojo**: A typed list holds only one element type. In this example, that type is Int. This allows the list implementation to pack data efficiently, using less space and improving performance. Mojo uses packed data rather than indirect references: ```mojo var nums: List[Int] = [1, 2, 3] nums.append(4) # [1, 2, 3, 4] ``` To store different kinds of values, define the element type as a `Variant` that enumerates permitted types. Variant lets a single element type represent multiple concrete value types: ```mojo from std.utils import Variant comptime MixedType = Variant[Int, Float64, String, Bool] var mixed_list = List[MixedType]() mixed_list.append(MixedType(42)) mixed_list.append(MixedType(3.14)) mixed_list.append(MixedType("hello")) mixed_list.append(MixedType(True)) for item in mixed_list: print(item) # Output lines: 42, 3.14, hello, and True ``` `Variant` tells the Mojo compiler which types are used, so it can allocate and manage memory correctly. ## Data structures: dictionaries Python dicts are dynamic. Keys and values can be anything. Mojo uses efficient dictionary implementations (Swiss tables) for fast data storage and retrieval. Typed dictionaries support efficient packing and data access. **Python**: ```python counts = {"a": 1, "b": "two"} counts["c"] = 3.0 # {'a': 1, 'b': 'two', 'c': 3.0} ``` **Mojo**: A typed declaration like `Dict[String, Int]` tells the compiler exactly what element types to expect for keys and values. This enables tighter, faster code. As with other Mojo collections, you can use `Variant` to broaden the range of permitted element types: ```mojo var counts: Dict[String, Int] = {"a": 1, "b": 2} counts["c"] = 3 # {a: 1, b: 2, c: 3} ``` ## Comprehensions Python's comprehensions have direct Mojo analogs. The syntax is essentially identical. **Mojo**: ```mojo var list_squares = [x * x for x in [0, 1, 2, 3, 4] if x % 2 == 0] # [0, 4, 16], list var positive_numbers = [x for x in range(-3, 3) if x > 0] # [1, 2], list var dict_squares = {x: x * x for x in range(3)} # {0: 0, 1: 1, 2: 4}, dict var upper_case = {k: v.upper() for k, v in [(1, "one"), (2, "two")]} # {1: ONE, 2: TWO}, dict var number_set = {x for x in range(5)} # {0, 1, 2, 3, 4}, set ``` ## Iteration In terms of syntax, Mojo's `for` and `while` loops align with Python. Use `break` and `continue` for control flow. **Mojo using a typed for-loop**: ```mojo var nums: List[Int] = [0, 1, 2, 3, 4] var squares2: List[Int] = [] for x in nums: if x % 2 == 0: squares2.append(x * x) print(squares2) # [0, 4, 16] ``` **Mojo using a while loop**: ```mojo var squares3: List[Int] = [] var idx = 0 while idx < 3: squares3.append(idx * idx) idx += 1 print(squares3) # [0, 1, 4] ``` ## Function definitions In Python, you can write a function without specifying the types of its arguments or return value. In Mojo, you must declare types explicitly. **Python**: ```python def add(a, b): return a + b ``` **Mojo**: ```mojo def add(a: Int, b: Int) -> Int: return a + b ``` The optional `->` syntax declares the return type. ## Error handling Error handling in Mojo looks very similar to Python. You raise and catch exceptions. **Python**: ```python try: raise ValueError("bad input") except ValueError as e: print(e) # bad input ``` The `raises` keyword in function and method declarations indicates that a function may generate or propagate errors. **Mojo**: ```mojo try: raise Error("bad input") except e: print(e) # bad input ``` You can specify error types by adding a type name after the `raises` keyword. This lets you catch the error and use the type instance directly in your `except` clause: ```mojo @fieldwise_init struct MyCustomError(Writable): var message: String def test_typed_error() raises MyCustomError: # Typed error raise MyCustomError("custom error occurred") try: test_typed_error() except e: print(e.message) # custom error occurred ``` Functions that don't handle the errors they raise automatically delegate error handling to their caller. You must declare these functions with the `raises` keyword: ```mojo def another_raising_function() raises: raise Error("Message") # Error raised here def raising_function() raises: another_raising_function() # Error continues to pass def handles_errors(): try: raising_function() # Error handled in this non-raising function except e: # handle error here ``` Note that in Mojo, each `try/except` statement can handle a single error type. ## Types: classes vs structs Python classes are flexible and dynamic. You can add attributes at runtime, mix types, and override behavior freely. Mojo uses `struct`, a statically typed alternative that the compiler can optimize aggressively. Mojo structs are stack-allocated. The value lives in a fast, fixed-size region of memory rather than on the heap, where a garbage collector must track and clean it up. **Python**: ```python class Point: def __init__(self, x, y): self.x = x self.y = y ``` **Mojo**: Mojo initializers require `out self`. The `out` keyword indicates that the method returns a value through an argument. In initializers, that argument is `self`, and the instance's fields are guaranteed to be fully initialized: ```mojo struct Point: var x: Int var y: Int def __init__(out self, x: Int, y: Int): self.x = x self.y = y ... def main(): var point = Point(5, 3) print(point.x, point.y) # 5, 3 ``` Structs deliver performance and predictability. ## Types: variable static typing **Python**: In Python, you may assign different types to the same variable. ```python a = "x" # String a = 10 # Not an error ``` **Mojo**: In Mojo, once a name is bound in a scope, its type is fixed and can't be rebound to a different type: ```mojo var a = 1 # Int a = "string" # Error: can't implicitly convert String to Int ``` When you use `Variant`, you can switch between the types it enumerates. The variable itself remains statically typed as a `Variant`, even though the concrete value it holds may change: ```mojo from std.utils import Variant from std.testing import * comptime StringOrInt = Variant[String, Int] var a: StringOrInt = 1 # Initial value, 1 assert_true(a.unsafe_get[Int]() == 1) a = "string" # Not an error, "string" assert_true(a.unsafe_get[String]() == "string") ``` ## Types and polymorphism: duck typing vs. traits In Python, duck typing means you don't declare what interface an object must have. If it has the method you call, it works at runtime. This is flexible, but it gives you no safety net. Mojo uses traits to solve the same problem explicitly. A trait defines the methods a type must implement or provides a default implementation. The compiler verifies that any type used in that role actually provides those methods, so mistakes surface before your code runs. **Python duck typing**: ```python def sketch(shape): shape.draw() # Works if shape has draw(), fails at runtime if not ``` **Mojo traits**: ```mojo trait Drawable: def draw(self): ... # required method def sketch[T: Drawable](shape: T): shape.draw() # Compiler guarantees shape has `draw()` ``` ## Memory management In Python, memory access is indirect, which adds overhead. Mojo uses direct memory access, speeding up execution. Python manages memory automatically using reference counting and a garbage collector. Reference counting deallocates objects when their count reaches zero. The garbage collector runs in the background to clean up objects that are no longer reachable. This removes the need to manage memory manually, but it also means you have no control over when collection happens. Mojo uses ownership semantics with ASAP ("as soon as possible") destruction. The compiler knows exactly when a value is used for the last time and its lifetime ends. Memory is freed at that point, without waiting for a garbage collector to run. If you need manual memory control, Mojo offers a suite of pointer and allocation options. You get convenience by default, and precise `alloc()` and `dealloc()` control when you reach for it. ## Python instincts that may surprise you in Mojo **"I don't need types."**: In Python, that's often fine. In Mojo, types are how the compiler generates fast, optimized code. Untyped code works in dynamic languages like Python, but it can leave performance, correctness, readability, and maintainability on the table. **"Everything is mutable."**: In Mojo, variables are mutable by default, but function and method arguments may not be. **"I can mix types in a list."**: Mojo collections use static types and benefit from the performance that brings. **"Threads are how I parallelize."**: Python threads are limited by the GIL. Mojo supports parallelism at multiple levels, from data-parallel SIMD operations to multi-threaded GPU execution. This isn't limited to threads. Mojo enables low-level SIMD parallelism and higher-level parallelism across GPUs. **"Classes are the natural way to structure things."**: Mojo's `struct` value type and its traits offer a better fit for performance-sensitive code. ## Use Mojo with AI coding assistants If you're using an AI coding assistant to help translate Python code to Mojo, install Mojo agent skills. The `mojo-python-interop` skill handles the patterns that trip models up like `PythonObject` wrapping, `import` conventions, and type conversions between the two languages. ```bash npx skills add modular/skills ``` This installs all four [Mojo agent skills](/docs/tools/skills), including `mojo-syntax` for general language accuracy. ## The Mojo mindset Mojo gives you Pythonic ergonomics with systems-level control. That control comes when you embrace types, ownership, and value semantics. You don't have to use all of it at once, but it's there for when you need it. --- ## Mojo quickstart Welcome to Mojo, a systems language for the AI era. This tutorial offers a quick tour of Mojo language fundamentals by showing you how to build a simple application. You'll get a taste of Mojo's syntax and enough familiarity to read and write basic Mojo code. It should take about 15-30 minutes if you stop to explore, or less if you work straight through. ## Setup Before starting: - Check the [system requirements](/docs/requirements/). - [Install Mojo](/install/) in a `pixi` or `uv` environment. - Open a terminal and make sure `mojo` is in your path or environment. As you work through this tour, look for *Takeaways* items. They connect new Mojo syntax to concepts you may already know from other languages. ## Hello Mojo Create `analyzer.mojo` in your favorite IDE or editor. Add this to `analyzer.mojo`: ```mojo def main(): print("Temperature Analyzer") ``` Run it: ```sh mojo analyzer.mojo ``` ### Takeaways - If you see "Temperature Analyzer", your setup works. - All Mojo executables use `main()` as their entry point. ## Variables and data Update your file to add temperature data: ```mojo def main(): print("Temperature Analyzer") # [Float64] sets the List element type at compile time var temps: List[Float64] = [20.5, 22.3, 19.8, 25.1] print("Recorded", len(temps), "temperatures") ``` ## Loops Print each temperature. Add to the end of `main()`: ```mojo def main(): # ... existing code ... for index in range(len(temps)): # The range is [0, len(temps)) print(t" Day {index + 1}: {temps[index]}°C") ``` ### Takeaways - This loop uses indexes. Normally you iterate over elements. - The `t"..."` prefix creates a *template string*. Braces `{}` interpolate expressions into the output. This avoids memory allocations for intermediate values. :::tip Worth knowing Mojo also has a `while` loop. ::: ## Functions Add a function above `main()` to calculate the average temperature: ```mojo def calculate_average(temps: List[Float64]) -> Float64: # A literal with a decimal component defaults to Float64 var total = 0.0 for temp in temps: total += temp return total / Float64(len(temps)) def main(): # ... existing code ... ``` Call the function by adding this to the end of `main()`: ```mojo var avg = calculate_average(temps) print(t"Average: {round(avg, 2)}°C") ``` ### Takeaways - Mojo's `def` functions don't raise by default. - `round()` returns `avg` rounded to two decimal places. ## Conditionals Classify the average temperature. Add to the end of `main()`: ```mojo if avg > 25.0: print("Status: Hot week") elif avg > 20.0: print("Status: Comfortable week") else: print("Status: Cool week") ``` ## Raise errors Empty data means no average. Update `calculate_average()` to handle the error: - Add `raises` before the return arrow. - Add an empty list check. - Raise an `Error` if it's empty. ```mojo def calculate_average(temps: List[Float64]) raises -> Float64: if len(temps) == 0: raise Error("No temperature data") var total = 0.0 for temp in temps: total += temp return total / Float64(len(temps)) ``` After updating, your app will no longer compile. Once `calculate_average()` can raise, its callers must handle or propagate the error. You do that in the next step. ## Handle errors Wrap failable code in `try-except` for error handling: ```mojo try: var avg = calculate_average(temps) print(t"Average: {round(avg, 2)}°C") if avg > 25.0: print("Status: Hot week") elif avg > 20.0: print("Status: Comfortable week") else: print("Status: Cool week") except e: print("Error:", e) ``` To test the error, replace `temps` with `[]`. Confirm that your app errors with "No temperature data". ### Takeaways - Each `try` statement requires at least one `except` or `finally` clause. - An `else` clause runs only if no error occurs. - A `finally` clause always runs. ```mojo try: operation() except e: handle_error(e) # Runs if an error occurs else: on_success() # Runs only if no error occurred finally: cleanup() # Always runs ``` ## Python integration Mojo integrates with libraries written in other languages. Use Python's NumPy to calculate the standard deviation. Install NumPy with your package manager: ```bash pixi add numpy ``` or: ```bash uv pip install numpy ``` Add these imports at the top of your file: ```mojo from std.python import Python from std.python.numpy import copy_to_numpy_array ``` Then calculate the standard deviation at the end of the `try` block in `main()`: ```mojo var np = Python.import_module("numpy") var pytemps = copy_to_numpy_array(temps) var std_dev = np.std(pytemps) print("Temperature standard deviation:", std_dev) ``` ## Final code Your complete `analyzer.mojo`: ```mojo from std.python import Python from std.python.numpy import copy_to_numpy_array def calculate_average(temps: List[Float64]) raises -> Float64: if len(temps) == 0: raise Error("No temperature data") var total = 0.0 for temp in temps: total += temp return total / Float64(len(temps)) def main(): print("Temperature Analyzer") var temps: List[Float64] = [20.5, 22.3, 19.8, 25.1] print("Recorded", len(temps), "temperatures") for index in range(len(temps)): print(t" Day {index + 1}: {temps[index]}°C") try: var avg = calculate_average(temps) print(t"Average: {round(avg, 2)}°C") if avg > 25.0: print("Status: Hot week") elif avg > 20.0: print("Status: Comfortable week") else: print("Status: Cool week") var np = Python.import_module("numpy") var pytemps = copy_to_numpy_array(temps) var std_dev = np.std(pytemps) print("Temperature standard deviation:", std_dev) except e: print("Error:", e) ``` ## What you touched You just used: Mojo variables, lists, loops, functions, conditionals, error handling, and Python integration in one working program. ## Your first day? Try these - Build Conway's Game of Life with the [get started tutorial](/docs/manual/get-started). It normally takes about 45-60 minutes. - Keep the [language reference](/docs/reference/) handy for syntax, keywords, and more. - Download the [cheat sheets](/docs/reference/cheat-sheets/) for printable reference cards that bring entire concepts together. - Play [Mojo Quest](https://quest.mojolang.org/) to practice Mojo syntax with coding challenges. ## Use Mojo with AI coding assistants If you use an AI coding assistant, install the Mojo agent skills to give it up-to-date information about the rapidly evolving language: ```sh npx skills add modular/skills ``` This installs all [Mojo agent skills](/docs/tools/skills), including the `mojo-syntax` skill for the latest nightly language releases. ## Keep going - Learn Mojo in depth with the [Mojo Manual](/docs/manual). - Look up APIs in the [Standard Library](/docs/std). - Ask questions and connect with other Mojo developers on the [Discourse forums](https://forum.modular.com/docs/community) and [Discord](https://discord.com/invite/modular). - Follow Mojo development on the [Mojo Blog](https://www.modular.com/blog). --- ## Mojo structs A struct is Mojo's primary way to define your own type. When you want to model both data and behavior—whether that's a small value type, a numeric abstraction, or the foundation of a larger system—use a struct. At a high level, a Mojo struct lets you bundle data together with the operations that act on that data. This makes structs a natural way to represent concepts in your program, rather than passing loosely related values through functions. Each Mojo `struct` is a data structure that lets you encapsulate _fields_ and _methods_ to store and operate on data. Structs can define the following members: - **Fields** are variables that store data relevant to the struct. - **Methods** are functions defined in a struct that normally act upon the field data. - **Static methods** are functions provided by the type to perform behaviors, provide constants, or create specialized instances. - **Dunder methods** are named for their _d_ouble _under_-scored form, with `__` on both sides. Also called "special methods," [they help define behaviors](/docs/manual/structs/#special-methods) such as initialization and allow structs to conform to [traits](/docs/manual/traits/). - **`comptime` members** enable compile-time references that can be used for optimization. For example, if you're building a graphics program, you can use a struct to define an `Image` that has fields to store information about each image (such as its component pixels) and methods that perform actions on it (such as rotating the image). Mojo's struct format is designed to provide a static, memory-safe data structure that's both powerful and performant. Unlike dynamic objects (such as Python classes) that can be modified freely at runtime, structs are defined at compile time, which allows Mojo to generate highly optimized code. All struct fields must be declared using `var` and include a type annotation. This requirement is part of Mojo's compile-time guarantees, helping ensure both performance and memory safety. ## Struct definition You can define a simple struct called `MyPair` with two fields like this: ```mojo struct MyPair: var first: Int var second: Int ``` However, you can't instantiate this struct because it has no initializer method. So here it is with an initializer to initialize the two fields: ```mojo struct MyPair: var first: Int var second: Int def __init__(out self, first: Int, second: Int): self.first = first self.second = second ``` Notice that the first argument in the `__init__()` method is `out self`. You'll have a `self` argument as the first argument on all struct methods. It references the current struct instance (it allows code in the method to refer to "itself"). _When you call the initializer, you never pass a value for `self`—Mojo passes it in automatically._ The `out` portion of `out self` is an [argument convention](/docs/manual/values/ownership#argument-conventions) that declares `self` as a mutable reference that starts out as uninitialized and must be initialized before the function returns. Many types use a field-wise initializer like the one shown for `MyPair` above: it takes an argument for each field, and initializes the fields directly from the arguments. To save typing, Mojo provides a [`@fieldwise_init`](/docs/reference/decorators/fieldwise-init/) decorator, which generates a field-wise initializer for the struct. So you can rewrite the `MyPair` example above like this: ```mojo @fieldwise_init struct MyPair: var first: Int var second: Int ``` The `__init__()` method is one of many [special methods](#special-methods) (also known as "dunder methods" because they have *d*ouble *under*scores) with pre-determined names. :::note You can't assign values when you declare fields. You must initialize all of the struct's fields in the initializer. (If you try to leave a field uninitialized, the code won't compile.) ::: ## Constructing a struct type Once you have an initializer, with `__init__()` or using `@fieldwise_init`, you can create an instance of `MyPair` and set the fields: ```mojo title="Construct an instance" var mine = MyPair(2, 4) print(mine.first) ``` ```output 2 ``` :::note Initializer lists Mojo initializer lists let you construct instances without spelling out the full type name and parameters. If the full type can be inferred from context, pass the initializer arguments directly between braces, with or without keywords. For example `{0.5, fish="salmon"}` calls `__init__(0.5, fish="salmon")` on the appropriate struct type. This is equivalent to `MyStruct(0.5, fish="salmon")` if the type is inferred to be `MyStruct`. ::: ## Making a struct Copyable {#making-a-struct-copyable-and-movable} By default, Mojo structs can be _moved_, but not _copied_. For example, the following code produces errors: ```mojo var a = MyPair(1, 2) # Implicit copy var b = a # value of type 'MyPair' cannot be implicitly copied, # it does not conform to 'ImplicitlyCopyable' # Explicit copy var c = a.copy() # 'MyPair' has no attribute 'copy' # it does not conform to 'Copyable' # Move var d = a^ # OK ``` In most cases, you can make a struct copyable just by adding the `Copyable` [trait](/docs/manual/traits/). ### Copyability To make a struct copyable, add the `Copyable` trait: ```mojo struct MyPair(Copyable): ... ``` In most cases, that's all you need to do. Mojo generates a copy initializer (`__init__(out self, *, copy: Self)` method) for you. You don't need to write your own unless you need custom logic in the copy initializer; for example, if your struct dynamically allocates memory. For more information, see the section on [copy initializers](/docs/manual/lifecycle/life/#copy-constructor). The [`Copyable`](/docs/std/traits/copyable/Copyable/) trait provides two ways to copy a value: the `copy()` instance method and the copy initializer. Prefer the `copy()` method. ### Implicit copyability To make a struct implicitly copyable, add the [`ImplicitlyCopyable`](/docs/std/traits/copyable/ImplicitlyCopyable/) trait: ```mojo struct MyPair(ImplicitlyCopyable): ... ``` `ImplicitlyCopyable` automatically implies `Copyable` and `Movable`, so all the notes related to copyability apply here. A type should only be implicitly copyable if copying the type is inexpensive and has no side effects. Unnecessary copies can be a big drain on memory and performance, so use this trait with caution. ## Fields Fields store a struct's data. When you declare a field, it becomes part of the struct's memory layout. Because the compiler knows every field's type at compile time, it can: - Calculate the struct's exact memory footprint - Ensure all fields are initialized before use - Generate fast, direct access to field data - Prevent changes to the struct's layout at runtime Fields share the lifetime of their struct instance. They are created when the struct is created and destroyed when the struct is destroyed. This model avoids dangling references and partially constructed objects. Outside of your struct implementation, you access fields with dot notation (`my_struct.field_name`). Within the struct, your methods access fields using `self` (`self.field_name`). Mojo knows each field's location at compile time, making field access direct and efficient. ### Field requirements **You must** declare field members with `var` in structs: ```mojo struct MyStruct: value: Int # Error. Missing `var` keyword var count: Int # Yes ``` Unlike local variables in functions, this requirement lets Mojo reason about a struct's layout and guarantees that its memory is safe and predictable. **You must** use unique symbols for fields, methods, or `comptime` members. These all exist in the same namespace: ```mojo struct MyStruct: var count: Int var count: String # Error. Invalid redeclaration of `count` ``` **You can** re-use a struct member's name for an argument or method variable. ```mojo struct MyStruct: var foo: Int def use_argument(self, foo: Int): # Argument shadows field print(foo) # Prints argument value def use_local(self, value: Int): var foo = value # Local variable shadows field print(foo, self.foo) # Prints local, then field ``` **You must** mark `self` as mutable if updating a field value. ```mojo struct MyStruct: var foo: Int def update_foo(mut self, new_value: Int): self.foo = new_value ``` **You must** initialize fields within initializers, and not at the point of declaration. ```mojo struct MyStruct: var foo: Int = 10 # Error: Unknown tokens comptime bar = 10 # Yes ``` [`comptime` members](/docs/manual/parameters/#comptime-members) are compile-time constants (not fields) and don't occupy instance storage, so they can be initialized at the point of declaration. ### Field conventions Like other Mojo elements, fields normally adhere to [certain conventions](https://github.com/modular/modular/blob/mojo/v1.1.0/Mojo/docs/contributing/stdlib/stdlib-code-style.md#code-conventions): You should use conventional naming for field members: - Prefer lowercase snake_case for field names (for example, `user_count`, `max_capacity`). - Use descriptive names that indicate purpose, and not their type (for example, `error_msg` not `msg_string`). - For members meant for internal use or to maintain invariants, add an underscore prefix (for example, `_private_field`). - For boolean fields, use `is_` or `has_` prefixes (for example, `is_valid`, `has_data`). - Avoid single-letter names except for common mathematical conventions (such as `x`, `y`, `z` for coordinates). ## Methods In addition to special methods like `__init__()`, you can add any other method you want to your struct. For example: ```mojo @fieldwise_init struct MyPair: var first: Int var second: Int def get_sum(self) -> Int: return self.first + self.second ``` ```mojo var mine = MyPair(6, 8) print(mine.get_sum()) ``` ```output 14 ``` Notice that `get_sum()` also uses the `self` argument, because this is the only way you can access the struct's fields in a method. The name `self` is just a convention, and you can use any name you want to refer to the struct instance that is always passed as the first argument. Methods that take the implicit `self` argument are called _instance methods_ because they act on an instance of the struct. :::note The `self` argument in a struct method is the only argument in a `def` function that doesn't require a type. You can include the type if you want, but you can elide it because Mojo already knows its type (`MyPair` in this case). ::: ## Mutating methods {#mutating-a-struct} By default, a struct's methods receive an immutable `self`, so they can't modify the struct's fields. For example: ```mojo struct MyStruct: var value: Int def increment(self): self.value += 1 # ERROR: expression must be mutable in assignment # ... ``` To allow a method to mutate the instance, declare its receiver as `mut self`. This makes `self` mutable inside the method and allows changes to its fields that persist after the method returns: ```mojo struct MyStruct: var value: Int def increment(mut self): self.value += 1 # Works: Mutable `self` allows assignment #... ``` :::note Read more about mutable arguments and the `mut` keyword: [Mutable arguments (`mut`)](/docs/manual/values/ownership/#mutable-arguments-mut) ::: ### Static methods A struct can have _static methods_. A static method can be called without creating an instance of the struct. Unlike instance methods, a static method doesn't receive the implicit `self` argument, so it can't access any fields on the struct. To declare a static method, use the [`@staticmethod`](/docs/reference/decorators/staticmethod/) decorator and don't include a `self` argument: ```mojo struct Logger: def __init__(out self): pass @staticmethod def log_info(message: String): print("Info: ", message) ``` You can invoke a static method by calling it on the type (in this case, `Logger`). You can also call it on an instance of the type. Both forms are shown below: ```mojo Logger.log_info("Static method called.") var l = Logger() l.log_info("Static method called from instance.") ``` ```output Info: Static method called. Info: Static method called from instance. ``` ## Structs compared to classes If you're familiar with other object-oriented languages, then structs might sound a lot like classes, and there are some similarities, but also some important differences. Eventually, Mojo will also support classes to match the behavior of Python classes. So, let's compare Mojo structs to Python classes. They both support methods, fields, operator overloading, decorators for metaprogramming, and more, but their key differences are as follows: - Python classes are dynamic: they allow for dynamic dispatch, monkey-patching (or "swizzling"), and dynamically binding instance fields at runtime. - Mojo structs are static: they are bound at compile-time (you cannot add methods at runtime). Structs allow you to trade flexibility for performance while being safe and easy to use. - Mojo structs don't support inheritance ("sub-classing"), but a struct can implement [traits](/docs/manual/traits/). - Python classes support class attributes—values that are shared by all instances of the class, equivalent to class variables or static data members in other languages. - Mojo structs don't support static data members. Syntactically, the biggest difference compared to a Python class is that all fields in a struct must be explicitly declared with `var`. In Mojo, the structure and contents of a struct are set at compile time and can't be changed while the program is running. Unlike in Python, where you can add, remove, or change attributes of an object on the fly, Mojo doesn't allow that for structs. However, the static nature of structs helps Mojo run your code faster. The program knows exactly where to find the struct's information and how to use it without any extra steps or delays at runtime. Mojo's structs also work really well with features you might already know from Python, like operator overloading (which lets you change how math symbols like `+` and `-` work with your own data, using [special methods](#special-methods)). As mentioned above, Mojo builds all its standard types ([`Int`](/docs/std/simd/#int), [`String`](/docs/std/collections/string/string/String/), etc.) from structs, rather than hardwiring them into the language itself. This gives you more flexibility and control when writing your code, and it means you can define your own types with all the same capabilities (there's no special treatment for the standard library types). ## Special methods Special methods (or "dunder methods") such as `__init__()` are pre-determined method names that you can define in a struct to perform a special task. Although it's possible to call special methods with their method names, the point is that you never should, because Mojo automatically invokes them in circumstances where they're needed (which is why they're also called "magic methods"). For example, Mojo calls the `__init__()` method when you create an instance of the struct; and when Mojo destroys the instance, it calls the `__deinit__()` method (if it exists). Even operator behaviors that appear built-in (`+`, `<`, `==`, `|`, and so on) are implemented as special methods that Mojo implicitly calls upon to perform operations or comparisons on the type that the operator is applied to. Mojo supports a long list of special methods; far too many to discuss here, but they generally match all of [Python's special methods](https://docs.python.org/3/reference/datamodel#special-method-names) and they usually accomplish one of two types of tasks: - Operator overloading: A lot of special methods are designed to overload operators such as `<` (less-than), `+` (add), and `|` (or) so they work appropriately with each type. For more information, see [Implement operators for custom types](/docs/manual/structs/operator-support/). - Lifecycle event handling: These special methods deal with the lifecycle and value ownership of an instance. For example, `__init__()` and `__deinit__()` demarcate the beginning and end of an instance lifetime, and other special methods define the behavior for other lifecycle events such as how to copy or move a value. You can learn all about the lifecycle special methods in the [Value lifecycle](/docs/manual/lifecycle/) section. However, most structs are simple aggregations of other types, so unless your type requires custom behaviors when an instance is created, copied, moved, or destroyed, you can synthesize the essential lifecycle methods you need (and save yourself some time) using the `@fieldwise_init` decorator (described in [Struct definition](#struct-definition)), and the `Copyable` and `Movable` traits (described in [Making a struct copyable](#making-a-struct-copyable-and-movable)). --- ## Add operator support to custom types Each Mojo operator maps to a set of dunder methods you can add to your struct implementation. These methods let you use operator syntax instead of calling methods directly. Knowing your operators and their related methods opens the full suite of operator syntax to your custom structs. ## Forward, reverse, and in-place methods Each binary operator uses up to three method forms. For example, consider the addition `a + b`: - **Forward**: Mojo tries `a.__add__(b)` first. - **Reverse**: If the forward method doesn't exist or can't handle `b`'s type, Mojo falls back to `b.__radd__(a)`. - **In-place**: For `a += b`, Mojo calls `a.__iadd__(b)`. Reversed methods exist for mixed-type expressions where the left operand doesn't know about the right operand's type: ```mojo a + 5 # calls a.__add__(5) 5 + a # Int doesn't know your type, falls back # to a.__radd__(5) a += 5 # calls a.__iadd__(5) ``` ## Unary operators A unary operator returns the original value if unchanged, or a new value representing the result. For example, `-x` uses the unary negation operator: ```mojo @fieldwise_init struct MyInt: var value: Int def __neg__(self) -> Self: return Self(-self.value) ``` If `x` is a `MyInt`, then `-x` returns a new instance with its `value` field negated. ## Comparison operators and traits Operators do not require that you conform your types to traits. However, there are benefits to doing so. The `Comparable` trait provides defaults for `<=`, `>`, and `>=`. You just implement `__lt__()` and `__eq__()`. Similarly, the `Equatable` trait provides defaults for `__eq__()` and `__ne__()` when all fields are `Equatable`. For types without a natural ordering (like complex numbers), only implement `Equatable`, and not `Comparable`. ## Subscript operators Implement `__getitem__()` for reads and `__setitem__()` for writes. Both subscripting methods accept variadic arguments for multi-dimensional indexing. For a simple one-dimensional collection, you unlock subscripting with a simple index: ```mojo struct MySeq[T: Copyable]: def __getitem__(self, idx: Int) -> T: ... def __setitem__(mut self, idx: Int, value: T): ... ``` For multi-dimensional collections, make use of variadics or multiple index arguments: ```mojo struct Grid[T: Copyable]: # Fixed two dimensions def __getitem__(self, x: Int, y: Int) -> T: ... # Arbitrary dimensions def __getitem__(self, *indices: Int) -> T: ... ``` Custom subscripts can support slicing as well as indices, such as `obj[1:5]`. Implement `__getitem__()` with a [`Slice`](/docs/std/builtin/builtin_slice/Slice/) parameter instead of `Int`. Each `Slice` has three optional fields: `start`, `end`, and `step`. You normalize these by calling `indices()`. Pass your type's size. This returns a triplet of values representing the span adjusted to your extent, resolving omitted values or negative indices into non-negative positions: ```mojo struct MySeq[T: Copyable]: var size: Int def __getitem__(self, span: Slice) -> Self: var start: Int var end: Int var step: Int start, end, step = span.indices(self.size) ... ``` ## Walkthrough: Build a `Complex` type The next sections incrementally build a `Complex` struct. This example demonstrates every category of operator implementation. In this walk-through, you'll work with unary operators, binary operators with same-type and mixed-type operands, reversed methods, in-place assignment, equality comparison, Boolean conversion, and subscript access. :::note The standard library includes [`ComplexSIMD`](/docs/std/complex/complex/ComplexSIMD/), a parameterized complex number type with basic arithmetic support. The `Complex` type in this example is independent and not based on `ComplexSIMD`. ::: ## Create the base type A complex number holds real and imaginary parts, stored in the real `re` and imaginary `im` fields: ```mojo from std.math import sqrt @fieldwise_init struct Complex( Boolable, Equatable, TrivialRegisterPassable, Writable, ): var re: Float64 var im: Float64 ``` - Conforming to `TrivialRegisterPassable` gives you value semantics without needing to write special lifecycle methods. - `Equatable` lets you compare two instances, and `Writable` produces output for `print()` statements. - `Boolable` lets you use a `Complex` value in a Boolean context, such as an `if` condition. ### Convenience initializer Adding a convenience initializer lets you create instances using only the real part of your number: ```mojo def __init__(out self, re: Float64): self.re = re self.im = 0.0 ``` ## Make your type printable Implementing `Writable` lets you use `print()` and `String()` directly. This custom implementation provides parentheses and separate real and imaginary output. ```mojo # Struct method def write_to(self, mut writer: Some[Writer]): writer.write("(", self.re) if self.im < 0: writer.write(" - ", -self.im) else: writer.write(" + ", self.im) writer.write("i)") ``` You can also implement `write_repr_to()` to define the value's *representation*—the developer-facing form returned by `repr()`. This implementation produces a string that mirrors how you'd construct the value in code: ```mojo # Struct method def write_repr_to(self, mut writer: Some[Writer]): t"Complex(re = {self.re}, im = {self.im})".write_to(writer) ``` ```mojo var c = Complex(3.14, -2.72) print(c) # (3.14 - 2.72i) print(repr(c)) # Complex(re = 3.14, im = -2.72) ``` ## Add unary operator support `+c` returns the value unchanged. `-c` negates both components: ```mojo # methods def __pos__(self) -> Self: return self def __neg__(self) -> Self: return Self(-self.re, -self.im) ... var c = Complex(-1.2, 6.5) print(+c) # (-1.2 + 6.5i) print(-c) # (1.2 - 6.5i) ``` ## Support binary arithmetic Add addition, subtraction, multiplication, and division between two `Complex` values with dunders. Each form returns a new `Complex` instance: ```mojo def __add__(self, rhs: Self) -> Self: return Self(self.re + rhs.re, self.im + rhs.im) def __sub__(self, rhs: Self) -> Self: return Self(self.re - rhs.re, self.im - rhs.im) def __mul__(self, rhs: Self) -> Self: return Self( self.re * rhs.re - self.im * rhs.im, self.re * rhs.im + self.im * rhs.re, ) def __truediv__(self, rhs: Self) -> Self: var denom = rhs.squared_norm() return Self( (self.re * rhs.re + self.im * rhs.im) / denom, (self.im * rhs.re - self.re * rhs.im) / denom, ) def squared_norm(self) -> Float64: return self.re * self.re + self.im * self.im def norm(self) -> Float64: return sqrt(self.squared_norm()) ``` ```mojo var c1 = Complex(-1.2, 6.5) var c2 = Complex(3.14, -2.72) print(c1 + c2) # (1.94 + 3.78i) print(c1 * c2) # (13.91 + 23.67i) ``` ## Add mixed-type arithmetic with reversed methods To support expressions like `2.5 + c` where `Float64` is on the left, you need both overloaded forward methods and reversed methods. Without `__radd__()`, `2.5 + c` would fail because `Float64` doesn't know about `Complex`: ```mojo # Forward: Complex + Float64 def __add__(self, rhs: Float64) -> Self: return Self(self.re + rhs, self.im) # Reversed: Float64 + Complex def __radd__(self, lhs: Float64) -> Self: return Self(self.re + lhs, self.im) def __sub__(self, rhs: Float64) -> Self: return Self(self.re - rhs, self.im) def __rsub__(self, lhs: Float64) -> Self: return Self(lhs - self.re, -self.im) def __mul__(self, rhs: Float64) -> Self: return Self(self.re * rhs, self.im * rhs) def __rmul__(self, lhs: Float64) -> Self: return Self(lhs * self.re, lhs * self.im) def __truediv__(self, rhs: Float64) -> Self: return Self(self.re / rhs, self.im / rhs) def __rtruediv__(self, lhs: Float64) -> Self: var denom = self.squared_norm() return Self( (lhs * self.re) / denom, (-lhs * self.im) / denom, ) ``` Now both orderings work: ```mojo var c = Complex(-1.2, 6.5) print(c + 2.5) # (1.3 + 6.5i) print(2.5 + c) # (1.3 + 6.5i) print(2.5 * c) # (-3.0 + 16.25i) ``` ### Allow in-place assignment In-place methods modify `self` directly instead of returning a new value. You can overload for both `Complex` and `Float64` operands: ```mojo def __iadd__(mut self, rhs: Self): self.re += rhs.re self.im += rhs.im def __iadd__(mut self, rhs: Float64): self.re += rhs def __isub__(mut self, rhs: Self): self.re -= rhs.re self.im -= rhs.im def __isub__(mut self, rhs: Float64): self.re -= rhs def __imul__(mut self, rhs: Self): var new_re = self.re * rhs.re - self.im * rhs.im var new_im = self.re * rhs.im + self.im * rhs.re self.re = new_re self.im = new_im def __imul__(mut self, rhs: Float64): self.re *= rhs self.im *= rhs def __itruediv__(mut self, rhs: Self): var denom = rhs.squared_norm() var new_re = (self.re * rhs.re + self.im * rhs.im) / denom var new_im = (self.im * rhs.re - self.re * rhs.im) / denom self.re = new_re self.im = new_im def __itruediv__(mut self, rhs: Float64): self.re /= rhs self.im /= rhs ... var c = Complex(-1.0, -1.0) c += Complex(0.5, -0.5) print(c) # (-0.5 - 1.5i) c += 2.75 print(c) # (2.25 - 1.5i) c *= 0.75 print(c) # (1.6875 - 1.125i) c /= 2.0 print(c) # (0.84375 - 0.5625i) ``` ## Support type equality checks Complex numbers have no natural ordering, so `Complex` conforms to `Equatable` (not `Comparable`). This gives you `==` and `!=` without implying that one complex number is "less than" another. You don't need to implement `__eq__()` or `__ne__()` yourself—implement them only when a type needs equality semantics that differ from a memberwise field comparison. `Equatable` supplies a default `__eq__()` that uses compile-time reflection to compare every field, and a default `__ne__()` that returns the inverse of `__eq__()`. A `Complex` is equal to another exactly when both fields match, so the reflection-based default is exactly the behavior you want. (Bear in mind that, because a floating-point `NaN` never equals itself, a `Complex` holding a `NaN` won't equal itself either.) ```mojo var c1 = Complex(-1.2, 6.5) var c2 = Complex(-1.2, 6.5) var c3 = Complex(3.14, -2.72) print(c1 == c2) # True print(c1 != c3) # True ``` ## Support use in Boolean contexts Conforming to `Boolable` and implementing `__bool__()` lets you use a `Complex` value directly in a Boolean context, such as an `if` condition or a call to `Bool()`. Mojo treats a built-in numeric value as "true" when it's nonzero, so a natural definition treats a complex number as "true" when either component is nonzero: ```mojo def __bool__(self) -> Bool: return self.re != 0.0 or self.im != 0.0 ``` ```mojo var c1 = Complex(0.0, 0.0) var c2 = Complex(-1.2, 6.5) print(Bool(c1)) # False print(Bool(c2)) # True if c2: print("c2 is nonzero") # c2 is nonzero ``` ## Unlock subscript access The get and set item dunders allow you to index content within your type. For this example, the real part of the complex number is index 0, and index 1 returns the imaginary component: ```mojo def __getitem__(self, idx: Int) raises -> Float64: if idx == 0: return self.re if idx == 1: return self.im raise "index out of bounds" def __setitem__(mut self, idx: Int, value: Float64) raises: if idx == 0: self.re = value elif idx == 1: self.im = value else: raise "index out of bounds" ... var c = Complex(3.14) print(c[0], c[1]) # 3.14 0.0 c[1] = 42.0 print(c) # (3.14 + 42.0i) ``` ## Every operator, one walkthrough This example walked you through every Mojo operator from simple arithmetic to comparisons to subscripting. Implementing the right dunders and/or conforming to the right traits enables you to use operator syntax for nearly any custom type. --- ## Self-referential structs Some data structures don't fit well with value semantics. Lists, trees, and graphs all need nodes that point to each other. You can't build these by nesting one value inside another, because the type would keep growing forever. In Mojo, you build these shapes with pointers, heap allocation, and manual cleanup. The idea may feel new at first, but the pattern stays simple once you see it in small steps. ## Avoid direct self-reference Mojo doesn't let you build a type that stores another instance of itself, even when nested within an [`Optional`](/docs/std/collections/optional/Optional/): ```mojo struct Node: var value: String var next: Optional[Node] # ERROR: Recursive reference # ... ``` Each `struct` has a fixed layout. If `Node` held another `Node` directly, the compiler wouldn't know how much space to reserve. Optional fields don't help, because the outer value still needs room for the inner one. Pointers solve this problem. Pointers have a fixed size, and they let values point at each other without blowing up the type. ## Adding self-referential pointers The following code shows how to set up a node that can point to its own type. This sample gives you a node type with a value slot and a single link to the next node: ```mojo struct Node[T: ImplicitlyCopyable & Writable & Deinitable]( Movable ): comptime NodePointer = Pointer[Self, MutUntrackedOrigin] var value: Optional[Self.T] # The `Node`'s value var next: Optional[Self.NodePointer] # Pointer to the next `Node` # Uses an `Optional` value to allow 'empty' Node construction # that can be moved into newly allocated memory def __init__(out self, value: Optional[Self.T] = None): self.value = value self.next = {} ``` The code defines a type-specific `NodePointer` type alias built on [`Pointer`](/docs/std/memory/pointer/Pointer/). [`MutUntrackedOrigin`](/docs/manual/values/lifetimes/) lets the pointer represent dynamically-allocated memory that the lifetime checker doesn't track. You need to both allocate and deallocate memory as needed. The `next` field is an `Optional[Self.NodePointer]` because a node may or may not link to another node. `Pointer` is non-nullable, so `Optional` provides the null state. `Optional[Pointer]` has the same memory layout as a raw pointer, so there's no overhead. For more on this pattern, see [Working with nullability](/docs/manual/pointers/using-pointers/#working-with-nullability). The optional `value` lets you create "empty" nodes, enabling you to move new `Node` memory allocations into place. ## Building nodes Here's the key pattern you can use in many reference structures: 1. Allocate space. 1. Construct a value-holding node. 1. Write it into the allocated memory. 1. Return the pointer. And here's an example of that pattern: ```mojo @staticmethod def make_node(value: Self.T) -> Self.NodePointer: var node_ptr = alloc[Self]({count = 1}).unsafe_leak() node_ptr.unsafe_write(Self(value)) return node_ptr ``` In this case, constructing the node (`Self(value)`) is simple enough that it's inline with the [`unsafe_write()`](/docs/std/memory/pointer/Pointer/#unsafe_write) call. This "allocate space, initialize, and write" approach creates safe pointer-based structures in Mojo. [`alloc()`](/docs/std/memory/alloc/alloc/) returns an [`Allocation`](/docs/std/memory/alloc/Allocation/), an owning handle that the compiler requires you to release before it goes out of scope. That's the right default, but a node has to outlive the function that allocates it, so `make_node()` calls [`unsafe_leak()`](/docs/std/memory/alloc/Allocation/#unsafe_leak) to take the raw pointer out of the handle. Leaking transfers responsibility for the memory to you — see [More memory allocation patterns](/docs/manual/pointers/using-pointers/#more-memory-allocation-patterns) for when to prefer each approach. ## Freeing nodes Releasing a node takes two steps: destroy the value stored in the memory, then release the memory itself. Because `make_node()` returns a raw pointer, you need to pair the leaked pointer back up with the layout you allocated it with to get an `Allocation` that [`dealloc()`](/docs/std/memory/alloc/dealloc/) can consume: ```mojo @staticmethod def free_node(var node_ptr: Self.NodePointer): node_ptr.unsafe_deinit_pointee() dealloc( ThinAllocation(unsafe_owned_ptr=node_ptr).unsafe_with_layout( {count = 1} ) ) ``` The two steps are separate because `dealloc()` releases memory without running deinitializers on whatever the memory holds. Skipping [`unsafe_deinit_pointee()`](/docs/std/memory/pointer/Pointer/#unsafe_deinit_pointee) would leak whatever the node's value owns, which in this case is a [`String`](/docs/std/collections/string/string/String/)'s heap buffer. Every place that removes a node calls this one method, so the pairing of `make_node()` and `free_node()` stays easy to audit. ## Linking nodes To link nodes, create a new node and set your `next` pointer to point at it. This example shows how to `append()` a new node using a supplied value. If a `next` node already exists, the code frees it before appending the new node. ```mojo def append(mut self, value: Self.T): # Free chain if replacing `next` if self.next: var next_ptr = self.next.value() next_ptr[].free_chain() Self.free_node(next_ptr) self.next = Self.make_node(value) ``` ## Walking the list To walk the list, follow the chain until you reach the end. Recursive code makes this easy to read. This example prints the value stored at each node: ```mojo @staticmethod def print_list(node: Optional[Self.NodePointer]): if not node: print("Empty list") return var node_ptr = node.value() var current_value: Optional[Self.T] = node_ptr[].value if current_value: print(current_value.value(), end=" ") if node_ptr[].next: Self.print_list(node_ptr[].next) else: print() ``` The pattern is simple: check the value, print it if it exists, then move to the next link. :::note This example uses a static method, but you can also implement it as an instance method. ::: ## Cleaning up Because you allocate each node yourself, you're also responsible for freeing it. This cleanup walks the chain and frees each node after destroying its pointee: ```mojo def free_chain(self): var current = self.next while current: var current_ptr = current.value() var next_node = current_ptr[].next Self.free_node(current_ptr) current = next_node ``` Note that the loop reads `current_ptr[].next` *before* freeing the node. Once `free_node()` returns, the pointer dangles and reading through it would be a use-after-free. The "head" node stays allocated unless you explicitly free it yourself: ```mojo list_head[].free_chain() ListNode.free_node(list_head) ``` ### Deinitializers When you build real Mojo data structures, you usually want a safe API that hides raw pointers from users. In a complete linked-list type (rather than a small demo of linkable nodes) the parent list handles node allocation and freeing. Because it owns the nodes, it also performs cleanup in its [deinitializer](/docs/manual/lifecycle/death/). Here's a small example that shows how to deinitialize `self`: ```mojo struct LinkedList[T: ImplicitlyCopyable & Writable & Deinitable]: comptime _Node = Node[T] var _head: Optional[Self._Node.NodePointer] def __deinit__(deinit self): """Clean up the list by freeing all nodes. Notes: Time complexity: O(n) in len(self). See Also: "Choose the form of the Destructor!" -- Gozer, "Ghostbusters" (1984). """ var curr = self._head while curr: var curr_ptr = curr.value() var next = curr_ptr[].next Self._Node.free_node(curr_ptr) curr = next ``` :::note Putting it together
View full sample code ```mojo from std.memory import ThinAllocation, dealloc comptime Element = String # Adapt for your type comptime ListNode = Node[Element] # Constructing a LinkedList struct Node[T: ImplicitlyCopyable & Writable & Deinitable](Movable): comptime NodePointer = Pointer[Self, MutUntrackedOrigin] var value: Optional[Self.T] # The `Node`'s value var next: Optional[Self.NodePointer] # Pointer to the next `Node` # Uses an `Optional` value to allow 'empty' Node construction # that can be moved into newly allocated memory def __init__(out self, value: Optional[Self.T] = None): self.value = value self.next = {} # Constructs a `Node` with a `value` with heap allocation and # returns a pointer to the new `Node`. @staticmethod def make_node(value: Self.T) -> Self.NodePointer: var node_ptr = alloc[Self]({count = 1}).unsafe_leak() node_ptr.unsafe_write(Self(value)) return node_ptr # Destroys the pointee, then releases the `Node`'s heap allocation by # pairing the leaked pointer back up with the layout it was allocated with. @staticmethod def free_node(var node_ptr: Self.NodePointer): node_ptr.unsafe_deinit_pointee() dealloc( ThinAllocation(unsafe_owned_ptr=node_ptr).unsafe_with_layout( {count = 1} ) ) # Constructs a `Node` with allocated memory, assigns a value, appends # the pointer to `self.next`. Replaces any existing `next`. def append(mut self, value: Self.T): # Free chain if replacing `next` if self.next: var next_ptr = self.next.value() next_ptr[].free_chain() Self.free_node(next_ptr) self.next = Self.make_node(value) # Prints the list starting at this pointer's pointee @staticmethod def print_list(node: Optional[Self.NodePointer]): if not node: print("Empty list") return var node_ptr = node.value() var current_value: Optional[Self.T] = node_ptr[].value if current_value: print(current_value.value(), end=" ") if node_ptr[].next: Self.print_list(node_ptr[].next) else: print() # Releases all successively allocated `Node` pointees. Does not release self. def free_chain(self): var current = self.next while current: var current_ptr = current.value() var next_node = current_ptr[].next Self.free_node(current_ptr) current = next_node def main(): var values: List[Element] = ["one", "one", "two", "three", "five", "eight"] var list_head = ListNode.make_node(values[0]) var current = list_head for idx in range(1, len(values), 1): current[].append(values[idx]) current = current[].next.value() ListNode.print_list(list_head) # Demonstrates cleanup. In short-lived programs, the OS reclaims memory # at exit list_head[].free_chain() ListNode.free_node(list_head) ``` Output: ```output one one two three five eight ```
::: ## What next? - Learn more about **pointers and memory safety** in Mojo's [using pointers](/docs/manual/pointers/using-pointers/) and [lifetime and origin rules](/docs/manual/values/lifetimes/) guides. - Learn more about how to **manage cleanup** in the Mojo [deinitializer](/docs/manual/lifecycle/death/) documentation. --- ## Traits Traits define contracts between types and the code that use them. Those contracts describe behavior, such as the methods a type must provide, as well as related (*associated*) types and constants. When a type conforms to a trait, the compiler verifies that it satisfies every requirement, allowing code to *rely* on the trait's interface. Traits are Mojo's pathway to polymorphism. They let you write code that works across many types without depending on implementation details that fall outside the contract. They're especially important for parameterized types and functions, which let you write code that works across many concrete types. By constraining a parameterized type to one or more traits, you give the compiler the information it needs to reason about the type and verify the code is safe and correct. Imagine you're writing code that works with many brands of sensors. Each sensor has its own implementation. They all know how to produce a reading, a run-time value such as a deflection angle. They also report a standard error range, a compile-time constant specific to that device. Each sensor also declares a measurement *type* that determines how the reading is interpreted. Some sensors measure angles, others distance or pressure. Your code shouldn't care how a particular sensor works or how it represents its measurements. An angle measurement, for example, might use degrees, radians, or gradians. It only needs to know that every sensor provides the operations and information it depends on. Traits express those requirements as a single contract. Instead of writing a function for each sensor type, you write one function against the trait, and any conforming type can use it. Mojo verifies that every required method, associated type, and compile-time value is present, so your code can use them without runtime overhead or capability checks. Traits are a foundation of Mojo code reuse. You write code in terms of what a trait requires rather than enumerating concrete types. That keeps your code flexible while preserving compile-time correctness. ## Defining traits Traits let the compiler reason about a type's shape and capabilities: its methods, associated types, and compile-time values. Declare a trait with the `trait` keyword, followed by a name and a block of requirements: ```mojo trait DeflectionSensing: def fetch_reading(self) -> Float64: ... ``` The three dots mark `fetch_reading()` as required. `DeflectionSensing` doesn't say how a type produces a value, only that a conforming type must be able to. ### Domain-specific behavior A trait can provide a default implementation based on the information it knows about conforming types. The default implementation can call other required methods, but it can't call methods that aren't part of the trait's contract. Default methods are a good fit for domain-specific behavior that can be expressed in terms of the trait's requirements. For example, `within_tolerance()` validates a sensor's current reading: ```mojo trait DeflectionSensing: def fetch_reading(self) -> Float64: ... comptime absolute_tolerance: Float64 = 0.05 # This is made up for this example def within_tolerance(self) -> Bool: return abs(self.fetch_reading()) <= Self.absolute_tolerance ``` Conforming types inherit default implementations and can override them. Mojo doesn't provide a way to call a default implementation from an override. ### Refining other traits A trait can refine another trait, meaning that it inherits every requirement from the refined trait while adding new ones. For example, `CalibratableDeflectionSensing` does everything `DeflectionSensing` does, but also requires a `calibrate()` method: ```mojo trait CalibratableDeflectionSensing(DeflectionSensing): def calibrate(mut self): ... struct EddyCurrentSensor(CalibratableDeflectionSensing): def fetch_reading(self) -> Float64: # its implementation def calibrate(mut self): # its implementation ``` A conforming `EddyCurrentSensor` must implement `calibrate()`, while also meeting every requirement of `DeflectionSensing`. It inherits the default implementation of `within_tolerance()`, which calls `fetch_reading()` and uses the `absolute_tolerance` constant. ## The trait contract A trait contract consists of methods and three kinds of compile-time members: associated types, required compile-time values, and shared compile-time constants. Methods can be required or provided. Required methods must be implemented by every conforming type. Provided methods include a default implementation that conforming types can override. Compile-time members either require each conforming type to provide its own value or define a value shared by every conforming type. - *Required methods* use the `...` ellipsis in their body. Every conforming type must implement them. ```mojo trait Loggable: def log(self, message: String): ... ``` - *Provided methods* are implemented in the trait. Conforming types can override them. Even a default no-op implementation is a valid provided method. ```mojo trait Pausable: def pause(self): pass ``` - *Associated types* require conforming types to declare a subordinate type. They're most commonly used in collections, where a parameterized collection declares an element type. ```mojo trait Container: associatedtype Element: Movable ``` - *Required compile-time values* must be defined by every conforming type. They're often used for values that vary across implementations. ```mojo trait Pausable: comptime max_pause_seconds: Float64 ``` - *Shared compile-time constants* are defined by the trait and shared by every conforming type. ```mojo trait DeflectionSensing: comptime absolute_tolerance: Float64 = 0.05 ``` A trait that declares none of these elements is called a *marker trait*. It doesn't require any methods, associated types, or compile-time values. Instead, it marks a conforming type as having a particular property or capability. ## Conforming to a trait A struct conforms to a trait by listing it in parentheses after the struct name and implementing its required methods: ```mojo @fieldwise_init struct CapacitiveSensor(Copyable, DeflectionSensing): def fetch_reading(self) -> Float64: # Not a very good sensor, but a simple example. return Float64(21.5) ``` If a struct claims to conform to `DeflectionSensing` but doesn't implement `fetch_reading()`, it won't compile. At compile time, Mojo verifies that `CapacitiveSensor` satisfies every `DeflectionSensing` requirement, including its methods and comptime elements. Traits don't use duck typing. A struct that implements `fetch_reading()` but doesn't declare `DeflectionSensing` isn't a conforming type. ### Required comptime members Define required comptime values directly on the conforming type with `comptime name = value`. For example, if `Pausable` requires a `max_pause_seconds` value, you'd declare it like this: ```mojo @fieldwise_init struct Timer(Copyable, Pausable): comptime max_pause_seconds: Float64 = 30.0 def pause(self): print("Paused") ``` ## Parameterizing functions and types with traits With the `DeflectionSensing` trait, you can build types for specific sensors, such as `CapacitiveSensor` or `EddyCurrentSensor`. By conforming to the trait, each type implements all required methods and comptime members. This shared contract lets you write a single function that works with any of them: ```mojo def averaged_poll[ SensorType: DeflectionSensing, // # infer-only ](sensor: SensorType, samples: Int) -> Float64: var total: Float64 = 0.0 for _ in range(samples): total += sensor.fetch_reading() return total / Float64(samples) ``` Since every sensor conforms to `DeflectionSensing`, the compiler knows that `fetch_reading()` is available: ```mojo var sensor = CapacitiveSensor() var average_reading = averaged_poll(sensor, 10) print("Average reading:", average_reading) # Fixed to 21.5 for the example ``` The call site doesn't use square brackets because the compiler infers `SensorType` from the argument. Use the `Some[]` shorthand when you don't need to name the type: ```mojo def averaged_poll_2(sensor: Some[DeflectionSensing], samples: Int) -> Float64: var total: Float64 = 0.0 for _ in range(samples): total += sensor.fetch_reading() return total / Float64(samples) ``` Use the named form when you need to refer to the type again, for example to require two arguments of the *same* conforming type: ```mojo def compare_readings[ SensorType: DeflectionSensing ](a: SensorType, b: SensorType) -> Float64: return a.fetch_reading() - b.fetch_reading() ``` ## Combining traits A parameter can require more than one trait. Use an ampersand (`&`) to combine them. Any type passed to the parameter must conform to every trait in the combination. For example, you could define a `Loggable` trait and require that a sensor conform to both `DeflectionSensing` and `Loggable`: ```mojo trait Loggable: def log(self, message: String): ... def poll_and_log[T: DeflectionSensing & Loggable](sensor: T): print(sensor.fetch_reading()) sensor.log("Polling sensor") ``` Refinement and composition solve different problems. Use refinement when one trait naturally extends another and that relationship should always hold. Use composition when a function or type needs multiple independent capabilities. ### Reusing trait compositions If you reuse the same combination in multiple places, give it a name with a `comptime` declaration: ```mojo comptime SensorLike = DeflectionSensing & Loggable struct SmartSensor(Copyable, SensorLike): def fetch_reading(self) -> Float64: return 18.2 def log(self, message: String): print("reading logged") ``` `SensorLike` isn't a new trait. It's shorthand for `DeflectionSensing & Loggable`. Any type that conforms to both traits automatically satisfies `SensorLike`; there's nothing extra to declare. ## Default implementations A trait can provide a working implementation instead of just requiring one: ```mojo trait DefaultLoggable: def log(self, message: String): print("reading logged") @fieldwise_init struct BasicSensor(Copyable, DefaultLoggable): pass ``` `BasicSensor` conforms without implementing `log()`. It inherits the trait's implementation, but any conforming type can override it by providing its own `log()`. Default implementations can conflict. If a type conforms to two traits that both provide the same method, Mojo won't choose between them: ```mojo trait PowerCycle: def restart(self): print("Restarting via power cycle") trait Rebootable: def restart(self): print("Restarting via soft reboot") struct Gateway(PowerCycle, Rebootable): pass # Error: conflicting default implementations for restart(). ``` Resolve the conflict by implementing `restart()` on `Gateway`. Your implementation overrides both defaults. ## Things to know **You can't add traits to existing types.** Conformance is declared where a type is defined. You can't retroactively make `Float64`, `Int`, or any other type you don't own conform to a new trait. **Conformance is explicit.** A struct that happens to implement `fetch_reading()` doesn't conform to `DeflectionSensing` unless it declares the trait. Mojo checks declared conformance, not just matching method names. **Traits are all or nothing.** A conforming type must satisfy every requirement, either by implementing it directly or by inheriting a default implementation. There's no partial conformance. --- ## Types All values in Mojo have an associated data type. Most of the types are *nominal* types, defined by a [`struct`](/docs/manual/structs/). These types are nominal (or "named") because type equality is determined by the type's *name*, not its *structure*. There are some types that aren't defined as structs: - Functions are typed based on their signatures. - `NoneType` is a type with one instance, the `None` object, which is used to signal "no value." Mojo comes with a standard library that provides a number of useful types and utility functions. These standard types aren't privileged. Each of the standard library types is defined just like user-defined types—even basic types like [`Int`](/docs/std/simd/#int) and [`String`](/docs/std/collections/string/string/String/). But these standard library types are the building blocks you'll use for most Mojo programs. The most common types are *built-in types*, which are always available and don't need to be imported. These include types for numeric values, strings, boolean values, and others. The standard library also includes many more types that you can import as needed, including collection types, utilities for interacting with the filesystem and getting system information, and so on. ## Numeric types Mojo provides built-in numeric types that represent signed integers, unsigned integers, and floating-point values. These types support multiple precisions and are used to model both low-level data and high-level numeric computation. The following sections introduce integer and floating-point types in Mojo. :::note All numeric types support the usual numeric and bitwise operators. The [`math`](/docs/std/math/) module provides additional math functions. ::: ### Integers and unsigned integers Mojo's general-purpose integer type is the signed `Int`. For a specific bit width, or for an unsigned integer, use the fixed-size integer types: - If you need a fixed-size integer, Mojo provides explicit-width integer types such as `Int8`, `Int16`, `UInt32`, and `UInt64`. - Use the general `Int` type when you don't require a specific bit width. These general and fixed-precision integer types are aliases to the [`SIMD`](/docs/std/simd/SIMD/) type. `Int` represents a signed integer that uses the system's native word size, typically 64 bits on 64-bit CPUs and 32 bits on 32-bit CPUs. You may wonder when to use `Int` and when to use the other integer types. In general, `Int` is a good safe default when you need an integer type and you don't require a specific bit width. Using `Int` as the default integer type for APIs makes APIs more consistent and predictable. #### Signed versus unsigned Signed and unsigned integers with the same bit width can represent the same number of distinct values, but over different ranges. For example: - `Int8` represents 256 values ranging from `-128` to `127` - `UInt8` represents 256 values ranging from `0` to `255` #### Overflow behavior Signed and unsigned integers differ in how they handle overflow. - When a signed integer overflows, the value wraps around into the negative range using two's complement arithmetic. For example, adding `1` to `var si: Int8 = 127` results in `-128`. - When an unsigned integer overflows, the value wraps around to the beginning of its range. For example, adding `1` to `var ui: UInt8 = 255` results in `0`. You may prefer unsigned integers when negative values are not required, when you are not designing a public API, or when you want to maximize the usable positive range. #### Mojo-supported fixed-width integer types
Table 1. Mojo signed integer types
| Type name | Description | |-----------|------------------------| | `Int8` | 8-bit signed integer | | `Int16` | 16-bit signed integer | | `Int32` | 32-bit signed integer | | `Int64` | 64-bit signed integer | | `Int128` | 128-bit signed integer | | `Int256` | 256-bit signed integer |
Table 2. Mojo unsigned integer types
| Type name | Description | |-----------|--------------------------| | `UInt8` | 8-bit unsigned integer | | `UInt16` | 16-bit unsigned integer | | `UInt32` | 32-bit unsigned integer | | `UInt64` | 64-bit unsigned integer | | `UInt128` | 128-bit unsigned integer | | `UInt256` | 256-bit unsigned integer |
### Floating-point numbers Mojo provides several floating-point types for representing real numbers at different precisions. Since floating-point values use a fixed number of bits, some numbers can't be represented exactly. The floating-point types `Float64`, `Float32`, and `Float16` follow the IEEE 754-2008 standard for representing floating-point values. Each type includes a sign bit, a set of bits representing an exponent, and a set of bits representing the mantissa (also called fraction or significand). Table 3 shows how these types are represented in memory.
Table 3. Details of floating-point types
| Type name | Sign | Exponent | Mantissa | |-----------|-------|----------|----------| | `Float64` | 1 bit | 11 bits | 52 bits | | `Float32` | 1 bit | 8 bits | 23 bits | | `Float16` | 1 bit | 5 bits | 10 bits |
Exponent values of all zeros or all ones represent special cases. These patterns allow floating-point numbers to encode positive and negative infinity, signed zeros, and not-a-number (NaN). These values are available as static constants provided by [`FloatLiteral`](/docs/std/builtin/float_literal/FloatLiteral/): ```mojo from std.math import copysign from std.utils.numerics import isfinite, isinf, isnan var inf = FloatLiteral.infinity print(isinf(inf)) # `True` print(inf > 0) # `True` var neginf = FloatLiteral.negative_infinity print(isinf(neginf)) # `True` print(neginf < 0) # `True` var nan = FloatLiteral.nan print(isnan(nan)) # `True` var negzero = FloatLiteral.negative_zero print(negzero == 0.0) # `True` print(copysign(1.0, negzero) < 0) # `True` ``` For more details on how floating-point numbers are represented, see [IEEE 754](https://en.wikipedia.org/wiki/IEEE_754). #### Floating-point approximations and comparisons Because floating-point values are approximate, they often cannot represent the exact mathematical value they are intended to model. - **Rounding errors.** Rounding may produce unexpected results. For example, `1/3` cannot be represented exactly in floating-point formats. As more floating-point operations are performed, rounding errors may accumulate. - **Space between consecutive numbers.** The distance between consecutive representable values varies across the range of a floating-point type. Near zero, values are densely packed. For large positive or negative numbers, the spacing can exceed 1, making it impossible to represent some consecutive integers. Because values are approximate, it is rarely useful to compare floating-point numbers using the equality operator (`==`). For example: ```mojo var big_num = 1.0e16 var bigger_num = big_num + 1.0 print(big_num == bigger_num) ``` ```output True ``` Comparison operators (such as `<` and `>=`) work as expected with floating-point values. To test whether two values are equal within a tolerance, use the [`math.isclose()`](/docs/std/math/math/isclose/) function to compare whether two floating-point numbers are equal within a specified tolerance. #### Mojo-supported floating-point types In the following table, the **eXmX** format (for example, `Float8_e5m2` and `Float8_e4m3fn`) refers to the number of bits allocated to a floating-point number's exponent and mantissa. All IEEE 754 floating-point formats use an implied leading 1. That means `Float32` is e8m23 but effectively e8m24, `Float16` is e5m10 but effectively e5m11, `BFloat16` is e8m7 but effectively e8m8, and `Float8_e4m3fn` is e4m3 but effectively e4m4. In addition to eXmX: - **fn** signifies finite numbers only. The numbers are valid floating-point values that are not infinite. NaN is supported. - **uz** means unsigned zero. Only +0 is supported, not -0. Although Mojo supports all these types, these types are not supported on all hardware. :::note The *B* in `BFloat16` stands for Brain, from the Google Brain artificial intelligence research group. Google developed it specifically for their Tensor Processing Units (TPUs) to accelerate machine learning workloads. Therefore the B is a project identifier and not a technical format indicator. :::
Table 4. Mojo floating-point types
| Type name | Description | CPU/GPU Support | |-------------------|------------------------------------------------------------------------------------------------------------------------|-----------------| | `Float16` | 16-bit floating-point(IEEE 754-2008 binary16) | CPU and GPU | | `Float32` | 32-bit floating-point(IEEE 754-2008 binary32) | CPU and GPU | | `Float64` | 64-bit floating-point(IEEE 754-2008 binary64) | CPU and GPU | | `BFloat16` | 16-bit floating-point(16-bit version of IEEE 754 binary32) | CPU and GPU | | `Float4_e2m1fn` | 4-bit floating-point(e2m1 format from Open Compute MX specification — finite values and NaN only, no infinities) | GPU | | `Float8_e5m2` | 8-bit floating-point(OFP8 e5m2 format) | GPU | | `Float8_e5m2fnuz` | 8-bit floating-point(AMD-only e5m2fnuz format — finite values and NaN only, no infinities) | GPU | | `Float8_e4m3fn` | 8-bit floating-point(OFP8 e4m3fn format — finite values and NaN only, no infinities) | GPU | | `Float8_e4m3fnuz` | 8-bit floating-point(AMD-only e4m3fnuz format — finite values and NaN only, no infinities) | GPU |
:::note GPU-only floating-point types are supported on specific accelerator hardware and may not be available on all GPUs. ::: #### AI-optimized floating-point formats Several floating-point types are specifically designed for AI and machine learning workloads, trading precision for memory efficiency and computational throughput. **BFloat16 (Brain Floating Point)** uses the same 8 exponent bits as `Float32`, preserving its dynamic range, but uses 7 explicit bits (8 effective bits) for the mantissa compared to Float32's 23 bits. This makes it ideal for neural network training where gradient magnitudes vary widely but high precision is less critical. **8-bit formats** (`Float8_e5m2`, `Float8_e4m3fn`, and their `fnuz` variants) are ultra-compact formats for AI accelerators where memory bandwidth is the primary bottleneck. The naming indicates bit allocation: `e5m2` means 5 exponent bits and 2 mantissa bits. The `fnuz` suffix additionally denotes unsigned zero (no -0), used in AMD hardware. **4-bit format** (`Float4_e2m1fn`) offers extreme compression with only 2 exponent bits and 1 mantissa bit, used in specialized inference scenarios where accuracy can be traded for maximum throughput. ### Numeric literals In addition to these numeric types, the standard libraries provides integer and floating-point literal types, [`IntLiteral`](/docs/std/builtin/int_literal/IntLiteral/) and [`FloatLiteral`](/docs/std/builtin/float_literal/FloatLiteral/). These literal types are used at compile time to represent literal numbers that appear in the code. In general, you should never instantiate these types yourself. Table 5 summarizes the literal formats you can use to represent numbers.
Table 5. Numeric literal formats
| Format | Examples | Notes | |------------------------|-----------------|--------------------------------------------------------------------------------------------------| | Integer literal | `1760` | Integer literal, in decimal format. | | Hexadecimal literal | `0xaa`, `0xFF` | Integer literal, in hexadecimal format.Hex digits are case-insensitive. | | Octal literal | `0o77` | Integer literal, in octal format. | | Binary literal | `0b0111` | Integer literal, in binary format. | | Floating-point literal | `3.14`, `1.2e9` | Floating-point literal.Must include the decimal point to be interpreted as floating-point. |
At compile-time, Mojo treats numeric literals as arbitrary-precision values, so the compiler can perform compile-time calculations without overflow or rounding errors. At runtime the values are converted to finite-precision types. `IntLiteral` can convert to any finite-precision integer type, defaulting to `Int` if the type is unspecified. And `FloatLiteral` converts to any finite-precision floating-point type, defaulting to `Float64`. ```mojo var float1 = 3.3 # float1 is type Float64 var float2: Float32 = 7.5 var int1 = 5 # int1 is type Int var int2: Int8 = 4 ``` This process of converting a value that can only exist at compile time into a runtime value is called *materialization*. The following code sample shows the difference between an arbitrary-precision calculation and the same calculation done using `Float64` values at runtime, which suffers from rounding errors. ```mojo var arbitrary_precision = 3.0 * (4.0 / 3.0 - 1.0) # use a variable to force the following calculation to occur at runtime var three = 3.0 var finite_precision = three * (4.0 / three - 1.0) print(arbitrary_precision, finite_precision) ``` ```output 1.0 0.99999999999999978 ``` ### `SIMD` and `DType` To support high-performance numeric processing, Mojo uses the [`SIMD`](/docs/std/simd/SIMD/) type as the basis for its numeric types. SIMD (single instruction, multiple data) is a processor technology that allows you to perform an operation on an entire set of operands at once. Mojo's `SIMD` type abstracts SIMD operations. A `SIMD` value represents a SIMD *vector*—that is, a fixed-size array of values that can fit into a processor's register. SIMD vectors are defined by two [*parameters*](/docs/manual/parameters/): - A `DType` value, defining the data type in the vector (for example, 32-bit floating-point numbers). - The number of elements in the vector, which must be a power of two. For example, you can define a vector of four `Float32` values like this: ```mojo var vec = SIMD[DType.float32, 4](3.0, 2.0, 2.0, 1.0) ``` Math operations on SIMD values are applied *elementwise*, on each individual element in the vector. For example: ```mojo var vec1 = SIMD[DType.int8, 4](2, 3, 5, 7) var vec2 = SIMD[DType.int8, 4](1, 2, 3, 4) var product = vec1 * vec2 print(product) ``` ```output [2, 6, 15, 28] ``` ### Scalar values The `SIMD` module defines several [`comptime` values](/docs/manual/metaprogramming/comptime-evaluation/#comptime-values) that function as *type aliases*—shorthand names for different `SIMD` vector types. The `Scalar` type is a `SIMD` vector with a single element. The numeric types, including signed integers such as `Int8` ([Table 1](#table-1)), unsigned integers such as `UInt16` ([Table 2](#table-2)), and floating-point values such as `Float32` ([Table 4](#table-4)), are type aliases for scalar values: ```mojo comptime Scalar = SIMD[length=1] comptime Int = Scalar[DType.int] comptime Int8 = Scalar[DType.int8] comptime Float32 = Scalar[DType.float32] ``` This means that whether you're working with a single `Float32` value or a vector of float32 values, the math operations go through exactly the same code path. #### The `DType` type The `DType` struct describes the different data types that a `SIMD` vector can hold, and defines a number of utility functions for operating on those data types. The `DType` struct defines a set of [`comptime` members](/docs/manual/parameters/#comptime-members) that act as identifiers for the different data types, like `DType.uint` and `DType.float32`. You use these `comptime` members when declaring a `SIMD` vector: ```mojo var v: SIMD[DType.float64, 16] ``` Note that `DType.float64` isn't a *type*, it's a value that describes a data type. You can't create a variable with the type `DType.float64`. You can create a variable with the type `SIMD[DType.float64, 1]` (or `Float64`, which is the same thing). ```mojo from std.utils.numerics import max_finite, min_finite def describeDType[dtype: DType](): print(dtype, "is floating-point:", dtype.is_floating_point()) print(dtype, "is integral:", dtype.is_integral()) print("Min/max finite values for", dtype) print(min_finite[dtype](), max_finite[dtype]()) describeDType[DType.float32]() ``` ```output float32 is floating-point: True float32 is integral: False Min/max finite values for float32 -3.4028234663852886e+38 3.4028234663852886e+38 ``` There are several other data types in the standard library that also use the `DType` abstraction. ### Numeric type conversion In Mojo, numeric [operators](/docs/manual/operators/) **don't** automatically narrow or widen operands to a common type. You need to explicitly convert the operands to the desired type. You can explicitly convert a `SIMD` value to a different `SIMD` type either by invoking its [`cast()`](/docs/std/simd/SIMD/#cast) method or by passing it as an argument to the initializer of the target type. For example: ```mojo var simd1 = SIMD[DType.float32, 4](2.2, 3.3, 4.4, 5.5) var simd2 = SIMD[DType.int16, 4](-1, 2, -3, 4) var simd3 = simd1 * simd2.cast[DType.float32]() # Convert with cast() method print("simd3:", simd3) var simd4 = simd2 + SIMD[DType.int16, 4]( simd1 ) # Convert with SIMD initializer print("simd4:", simd4) ``` ```output simd3: [-2.2, 6.6, -13.200001, 22.0] simd4: [1, 5, 1, 9] ``` You can convert a `Scalar` value by passing it as an argument to the initializer of the target type. For example: ```mojo var my_int: Int16 = 12 # SIMD[DType.int16, 1] var my_float: Float32 = 0.75 # SIMD[DType.float32, 1] var result = Float32(my_int) * my_float # Result is SIMD[DType.float32, 1] print("Result:", result) ``` ```output Result: 9.0 ``` You can convert a scalar value of any numeric type to `Int` by passing the value to the [`Int()`](/docs/std/simd/SIMD/#__init__) initializer method. Additionally, you can pass an instance of any struct that implements the [`Intable`](/docs/std/builtin/int/Intable/) trait or [`IntableRaising`](/docs/std/builtin/int/IntableRaising/) trait to the `Int()` initializer to convert that instance to an `Int`. ## Strings Strings are Mojo's primary text type. They store UTF-8 encoded text and provide a safe, ergonomic interface for string manipulation. Mojo's `String` type is a mutable string. `String` supports a variety of operators and common methods: ```mojo var s: String = "Testing" s += " Mojo strings" print(s) # Testing Mojo strings ``` ### Construction Many standard library types conform to the [`Writable`](/docs/std/format/Writable/) trait, which indicates that a value can be converted into a `String` using the `String(...)` initializer. The built-in [`print()`](/docs/std/io/io/print/) function accepts values that conform to the `Writable` trait. Use `String(value)` to explicitly convert a value to a `String`: ```mojo var s = "Items in list: " + String(5) print(s) # Items in list: 5 ``` Or, use the string initializer with variadic `Writable` types, so you don't have to call `String()` on each value: ```mojo var s = String("Items in list: ", 5) print(s) # Items in list: 5 ``` ### Emoji and grapheme clusters Mojo source files are UTF-8, letting you write emoji and other non-ASCII characters directly inside string literals. ```mojo var wave = "👋" ``` The standard library counts emoji three different ways, and the answers usually disagree: - `byte_length()` returns the number of UTF-8 bytes. - `count_codepoints()` counts the Unicode code points. - `count_graphemes()` returns the number of user-perceived characters (grapheme clusters), following [UAX #29](https://www.unicode.org/reports/tr29/). The grapheme count is what matches what a human would tell you if you asked them to "count characters." A family emoji, a flag, and a waving hand with a skin tone are each one grapheme, even though each is built from several joined code points: ```mojo def show(label: StaticString, s: StringSlice): print( label, "bytes=", s.byte_length(), "codepoints=", s.count_codepoints(), "graphemes=", s.count_graphemes(), ) def main(): show("family ", "👨‍👩‍👧‍👦") show("flag ", "🇺🇸") show("wave ", "👋🏽") show("namaste ", "नमस्ते") ``` Output: ```text family bytes=25 codepoints=7 graphemes=1 flag bytes=8 codepoints=2 graphemes=1 wave bytes=8 codepoints=2 graphemes=1 namaste bytes=18 codepoints=6 graphemes=3 ``` If you'd rather not embed non-ASCII bytes in your source, for example, in ASCII-only codebases, you can spell the code point with a hex escape or `chr()`: ```mojo var wave = "\U0001F44B" # 8-digit hex escape var wave2 = chr(0x1F44B) # chr() function var copy = "\u00A9" # 4-digit hex escape, © var euro = "\u20AC" # 4-digit hex escape, € ``` Mojo's 4-digit `\uHHHH` escape spells code points up to U+FFFF. The 8-digit `\U` form extends to U+10FFFF, Unicode's upper limit, covering emoji like 👋 (U+1F44B) and other characters above the Basic Multilingual Plane. The `chr()` function works for the full range too. Both forms reject surrogate code points (U+D800 to U+DFFF). Surrogates are reserved for UTF-16 encoding and aren't valid on their own. To escape a character above U+FFFF, write its full code point with `\U`. Don't use a UTF-16 surrogate pair. ### String formatting The `format()` method inserts values into a string using manual or automatic positional indexing. Replacement fields use braces: ```mojo print("{0} {1} {0}".format("Mojo", 1.125)) # Mojo 1.125 Mojo print("{} {}".format(True, "hello world")) # True hello world ``` Mojo's `TString` (template string) replaces the functionality of the `format()` method with direct expressions and better performance characteristics. `TString`s work like `format()`, but they insert [`Writable`](/docs/std/format/Writable/) representations of expressions into replacement fields. This provides safe and flexible string processing: ```mojo var count = 3 var items = "apples" var template = t"Give me {count} {items}." # Template string print(template) # Output: Give me 3 apples. ``` `TString` values are lazy. They don't allocate until you explicitly construct a `String`: ```mojo var x = 41 print(t"The answer is {x + 1}") # The answer is 42 (no allocation) var name = "Nate" var template = t"Hello, {name}!" # template creation print(template) # Hello, Nate! (no allocation) var s = String(template) # explicitly construct a string ``` `TString`s can add arbitrary expressions within the replacement fields: ```mojo var list: List[Int] = [1, 2, 3] print(t"{list[0] + list[1]}") # 3 ``` ### Raw strings In Mojo, **raw strings** are string literals prefixed with `r`. If you ran the following command it would print on one line, not two, because raw strings prevent backslash escape sequences from being interpreted: ```mojo print(r"Hello\nWorld") # Hello\nWorld, with the backslash and n ``` Raw strings help with regular expressions, code generation, serialization code, and other applications where you want to use escape sequences as literal entries in your string. Unlike normal strings, escapes aren't processed. Rawness applies to all forms of strings: single line, multi-line, docstrings, and `TString`s. Raw `TString`s support interpolation but won't expand escape sequences: ```mojo var name = "Nate" print(rt"Hello,\t{name}.") # Hello,\tNate., with the backslash and t ``` Raw strings *still* need a way to terminate, so if you have to use `"` within your content, use an alternate form of quote, such as single quote (`'`) or triple quotes, (`"""`, `'''`) to enclose it: ```mojo r'She said, "Hello, World!"' ``` ### String literals As with numeric types, the standard library includes a string literal type used to represent literal strings in the program source. String literals are enclosed in either single or double quotes. Adjacent literals are concatenated together, so you can define a long string using a series of literals broken up over several lines: ```mojo comptime s = "A very long string which is " "broken into two literals for legibility." ``` To define a multi-line string, enclose the literal in three single or double quotes: ```mojo comptime s = """ Multi-line string literals let you enter long blocks of text, including newlines.""" ``` Note that the triple double quote form is also used for API documentation strings. A `StringLiteral` will materialize to a `String` when used at run-time: ```mojo comptime param = "foo" # type = StringLiteral var runtime_value = "bar" # type = String var runtime_value2 = param # type = String ``` ## Booleans Mojo's `Bool` type represents a boolean value. It can take one of two values, `True` or `False`. You can negate a boolean value using the `not` operator. ```mojo var conditionA = False var conditionB: Bool conditionB = not conditionA print(conditionA, conditionB) ``` ```output False True ``` Many types have a boolean representation. Any type that implements the [`Boolable`](/docs/std/builtin/bool/Boolable/) trait has a boolean representation. As a general principle, collections evaluate as True if they contain any elements, False if they are empty; strings evaluate as True if they have a non-zero length. ## Tuples Mojo's `Tuple` is a lightweight, fixed-size, heterogeneous collection with value semantics. A tuple contains zero or more comma-separated values, which may have different types. Although a tuple's structure (its size and element types) is fixed, individual elements can be mutated. Tuples support several forms of indexing: ```mojo # Tuples can hold multiple types var example_tuple = Tuple[Int, String](1, "Example") # Assign multiple variables at once var x, y = example_tuple print(x, y) # Get individual values with an index var s = example_tuple[1] print(s) ``` ```output 1 Example Example ``` You can also create a tuple without explicit typing. ```mojo var example_tuple = (1, "Example") var s = example_tuple[1] print(s) ``` ```output Example ``` ## Collection types The Mojo standard library also includes a set of basic collection types that can be used to build more complex data structures: - [`List`](/docs/std/collections/list/List/), a dynamically-sized array of items. - [`Dict`](/docs/std/collections/dict/Dict/), an associative array of key-value pairs. - [`Set`](/docs/std/collections/set/Set/), an unordered collection of unique items. - [`Optional`](/docs/std/collections/optional/Optional/) represents a value that may or may not be present. The collection types are *parameterized types*: while a given collection can only hold a specific type of value (such as `Int` or `Float64`), you specify the type at compile time using a [parameter](/docs/manual/parameters/). For example, you can create a `List` of `Int` values like this: ```mojo var l: List[Int] = [1, 2, 3, 4] # l.append(3.14) # error: FloatLiteral cannot be converted to Int ``` You don't always need to specify the type explicitly. If Mojo can *infer* the type, you can omit it. For example, when you construct a list from a set of integer literals, Mojo creates a `List[Int]`. ```mojo # Inferred type == List[Int] var l1: List = [1, 2, 3, 4] ``` Where you need a more flexible collection, the [`Variant`](/docs/std/utils/variant/Variant/) type can hold different types of values. For example, a `Variant[Int32, Float64]` can hold either an `Int32` *or* a `Float64` value at any given time. (Using `Variant` is not covered in this section, see the [API docs](/docs/std/utils/variant/Variant/) for more information.) The following sections give brief introduction to the main collection types. ### List [`List`](/docs/std/collections/list/List/) is a dynamically-sized array of elements. You can create a `List` by passing the element type as a parameter, like this: ```mojo var l = List[String]() ``` The `List` type supports a subset of the Python `list` API, including the ability to append to the list, pop items out of the list, and access list items using subscript notation. ```mojo var list: List[Int] = [2, 3, 5] list.append(7) list.append(11) print("Popping last item from list: ", list.pop()) for idx in range(len(list)): print(list[idx], end=", ") ``` ```output Popping last item from list: 11 2, 3, 5, 7, ``` Note that the previous code sample leaves out the type parameter when creating the list. Because the list is being created with a set of `Int` values, Mojo can *infer* the type from the arguments. - Mojo supports list, set, and dictionary literals for collection initialization: ```mojo # List literal, element type infers to Int. var nums: List = [2, 3, 5] ``` You can also use an explicit type if you want a specific element type: ```mojo var list : List[UInt8] = [2, 3, 5] ``` You can also use list "comprehensions" for compact conditional initialization: ```mojo var list2 = [x*Int(y) for x in nums for y in list if x != 3] ``` - You can't `print()` a list, or convert it directly into a string. ```mojo # Does not work print(list) ``` As shown above, you can print the individual elements in a list as long as they're a [`Writable`](/docs/std/format/Writable/) type. - Iterating a `List` returns an immutable [reference](/docs/manual/values/lifetimes/#working-with-references) to each item: ```mojo var list: List[Int] = [2, 3, 4] for item in list: print(item, end=", ") ``` ```output 2, 3, 4, ``` If you would like to mutate the elements of the list, capture the reference to the element with `ref` instead of making a copy: ```mojo var list: List[Int] = [2, 3, 4] for ref item in list: # Capture a ref to the list element print(item, end=", ") item = 0 # Mutates the element inside the list print("\nAfter loop:", list[0], list[1], list[2]) ``` ```output 2, 3, 4, After loop: 0 0 0 ``` You can see that the original loop entries were modified. ### Dict The [`Dict`](/docs/std/collections/dict/Dict/) type is an associative array that holds key-value pairs. You can create a `Dict` by specifying the key type and value type as parameters and using dictionary literals: ```mojo # Empty dictionary var empty_dict: Dict[String, Float64] = {} # Dictionary with initial key-value pairs var values: Dict[String, Float64] = {"pi": 3.14159, "e": 2.71828} ``` You can also use the initializer syntax: ```mojo var values = Dict[String, Float64]() ``` The dictionary's key type must conform to the [`KeyElement`](/docs/std/collections/dict/#keyelement) trait, and value elements must conform to the [`Copyable`](/docs/std/traits/copyable/Copyable/) trait. You can insert and remove key-value pairs, update the value assigned to a key, and iterate through keys, values, or items in the dictionary. The `Dict` iterators all yield [references](/docs/manual/values/lifetimes/#working-with-references), which are copied into the declared name by default, but you can use the `ref` marker to avoid the copy: ```mojo var d: Dict[String, Float64] = { "plasticity": 3.1, "elasticity": 1.3, "electricity": 9.7 } for item in d.items(): print(item.key, item.value) ``` ```output plasticity 3.1000000000000001 elasticity 1.3 electricity 9.6999999999999993 ``` This is an unmeasurable micro-optimization in this case, but is useful when working with types that aren't `Copyable`. ### Set The [`Set`](/docs/std/collections/set/Set/) type represents a set of unique values. You can add and remove elements from the set, test whether a value exists in the set, and perform set algebra operations, like unions and intersections between two sets. Sets are parameterized and the element type must conform to the [`KeyElement`](/docs/std/collections/dict/#keyelement) trait. Like lists and dictionaries, sets support standard literal syntax, as well as generator comprehensions: ```mojo var i_like = {"sushi", "ice cream", "tacos", "pho"} var you_like = {"burgers", "tacos", "salad", "ice cream"} var we_like = i_like.intersection(you_like) print("We both like:") for item in we_like: print("-", item) ``` ```output We both like: - ice cream - tacos ``` ### Optional An [`Optional`](/docs/std/collections/optional/Optional/) represents a value that may or may not be present. Like the other collection types, it is parameterized, and can hold any type that conforms to the [`Copyable`](/docs/std/traits/copyable/Copyable/) trait. ```mojo # Two ways to initialize an Optional with a value var opt1 = Optional(5) var opt2: Optional[Int] = 5 # Two ways to initialize an Optional with no value var opt3 = Optional[Int]() var opt4: Optional[Int] = None ``` An `Optional` evaluates as `True` when it holds a value, `False` otherwise. If the `Optional` holds a value, you can retrieve a reference to the value using the `value()` method. But calling `value()` on an `Optional` with no value results in undefined behavior, so you should always guard a call to `value()` inside a conditional that checks whether a value exists. ```mojo var opt: Optional[String] = "Testing" if opt: var value_ref = opt.value() print(value_ref) ``` ```output Testing ``` Alternately, you can use the `or_else()` method, which returns the stored value if there is one, or a user-specified default value otherwise: ```mojo var custom_greeting: Optional[String] = None print(custom_greeting.or_else("Hello")) # Hello custom_greeting = "Hi" print(custom_greeting.or_else("Hello")) # Hi ``` --- ## Intro to value ownership A program is nothing without data, and all modern programming languages store data in one of two places: the call stack and the heap (also sometimes in CPU registers, but we won't get into that here). However, each language reads and writes data a bit differently—sometimes very differently. So in the following sections, we'll explain how Mojo manages memory in your programs and how this affects the way you write Mojo code. ## Stack and heap overview In general, all modern programming languages divide a running program's memory into four segments: - Text. The compiled program. - Data. Global data, either initialized or uninitialized. - Stack. Local data, automatically managed during the program's runtime. - Heap. Dynamically-allocated data, managed by the programmer. The text and data segments are statically sized, but the stack and heap change size as the program runs. The *stack* stores data local to the current function. When a function is called, the program allocates a block of memory—a *stack frame*—that is exactly the size required to store the function's data, including any *fixed-size* local variables. When another function is called, a new stack frame is pushed onto the top of the stack. When a function is done, its stack frame is popped off the stack. Notice that we said only "*fixed-size* local values" are stored in the stack. Dynamically-sized values that can change in size at runtime are instead stored in the heap, which is a much larger region of memory that allows for dynamic memory allocation. Technically, a local variable for such a value is still stored in the call stack, but its value is a fixed-size pointer to the real value on the heap. Consider a Mojo string: it can be any length, and its length can change at runtime. So the Mojo `String` struct includes some statically-sized fields, plus a pointer to a dynamically-allocated buffer holding the actual string data. Another important difference between the heap and the stack is that the stack is managed automatically—the code to push and pop stack frames is added by the compiler. Heap memory, on the other hand, is managed by the programmer explicitly allocating and deallocating memory. You may do this indirectly—by using standard library types like `List` and `String`—or directly, using the [`alloc()`](/docs/std/memory/alloc/alloc/) and [`Pointer`](/docs/std/memory/pointer/Pointer/) APIs. Values that need to outlive the lifetime of a function (such as an array that's passed between functions and should not be copied) are stored in the heap, because heap memory is accessible from anywhere in the call stack, even after the function that created it is removed from the stack. This sort of situation—in which a heap-allocated value is used by multiple functions—is where most memory errors occur, and it's where memory management strategies vary the most between programming languages. ## Memory management strategies Because memory is limited, it's important that programs remove unused data from the heap ("free" the memory) as quickly as possible. Figuring out when to free that memory is pretty complicated. Some programming languages try to hide the complexities of memory management from you by utilizing a "garbage collector" process that tracks all memory usage and deallocates unused heap memory periodically (also known as automatic memory management). A significant benefit of this method is that it relieves developers from the burden of manual memory management, generally avoiding more errors and making developers more productive. However, it incurs a performance cost because the garbage collector interrupts the program's execution, and it might not reclaim memory very quickly. Other languages require that you manually free data that's allocated on the heap. When done properly, this makes programs execute quickly, because there's no processing time consumed by a garbage collector. However, the challenge with this approach is that programmers make mistakes, especially when multiple parts of the program need access to the same memory—it becomes difficult to know which part of the program "owns" the data and must deallocate it. Programmers might accidentally deallocate data before the program is done with it (causing "use-after-free" errors), or they might deallocate it twice ("double free" errors), or they might never deallocate it ("leaked memory" errors). Mistakes like these and others can have catastrophic results for the program, and these bugs are often hard to track down, making it especially important that they don't occur in the first place. Mojo uses a third approach called "ownership" that relies on a collection of rules that programmers must follow when passing values. The rules ensure there is only one "owner" for a given value at a time. When a value's lifetime ends, Mojo calls its deinitializer, which is responsible for deallocating any heap memory that needs to be deallocated. In this way, Mojo helps ensure memory is freed, but it does so in a way that's deterministic and safe from errors such as use-after-free, double-deallocation and memory leaks. Plus, it does so with a very low performance overhead. Mojo's value ownership model provides an excellent balance of programming productivity and strong memory safety. It only requires that you learn some new syntax and a few rules about how to share access to memory within your program. But before we explain the rules and syntax for Mojo's value ownership model, you first need to understand [value semantics](/docs/manual/values/value-semantics). --- ## Lifetimes, origins, and references The Mojo compiler includes a lifetime checker, a compiler pass that analyzes dataflow through your program. It identifies when variables are valid and inserts deinitializer calls when a variable's lifetime ends. The Mojo compiler uses a special value called an *origin* to track the lifetime of variables and the validity of references. Specifically, an origin answers two questions: - What variable "owns" this value? - Can the value be mutated using this reference? For example, consider the following code: ```mojo def print_str(s: String): print(s) def main(): var name: String = "Joan" print_str(name) ``` ```output Joan ``` The line `name = "Joan"` declares a variable with an identifier (`name`) and logical storage space for a `String` value. When you pass `name` into the `print_str()` function, the function gets an immutable reference to the value. So both `name` and `s` refer to the same logical storage space, and have associated origin values that lets the Mojo compiler reason about them. Origin tracking and lifetime checking is done at compile time, so origins don't track the actual storage space allocated for the `name` variable, for example. Instead, origins track variables symbolically, so the compiler tracks that `print_str()` is called with a value owned by `name` in the caller's scope. By tracking how owned data flows through the program, the compiler can identify the lifetimes of values. Most of the time, origins are handled automatically by the compiler. However, in some cases you'll need to interact with origins directly: - When working with references—specifically `ref` arguments and `ref` return values. - When working with types like [`Pointer`](/docs/std/memory/pointer/Pointer/) or [`Span`](/docs/std/collections/span/Span/) which are parameterized on the origin of the data they refer to. This section also covers [`ref` arguments](#ref-arguments) and [`ref` return values](#ref-return-values), which let functions take arguments and provide return values as references with parametric origins. ## Working with origins Mojo's origin values are mostly created by the compiler, so you can't just create your own origin value—you usually need to derive an origin from an existing value. Among other things, Mojo uses origins to extend the lifetimes of referenced values, so values aren't destroyed prematurely. ### Origin types Mojo supplies a struct and a set of type aliases (`comptime` values) that you can use to specify origin types. As the names suggest, the `ImmOrigin` and `MutOrigin` `comptime` values represent immutable and mutable origins, respectively: ```mojo struct ImmutRef[origin: ImmOrigin]: pass ``` Or you can use the [`Origin`](/docs/std/origin/Origin/) struct to specify an origin with parametric mutability: ```mojo struct ParametricRef[ is_mutable: Bool, //, origin: Origin[mut=is_mutable] ]: pass ``` Origin types carry the mutability of a reference as a boolean parameter value, indicating whether the origin is mutable, immutable, or even with mutability depending on a parameter specified by the enclosing API. The `is_mutable` parameter here is an [infer-only parameter](/docs/manual/parameters/#infer-only-parameters). The `origin` value is often inferred, as well. For example, the following code creates a [`Pointer`](/docs/std/memory/pointer/Pointer/) to an existing value, but doesn't need to specify an origin—the `origin` is inferred from the existing value. ```mojo from std.memory import Pointer def use_pointer(): var a = 10 var ptr = Pointer(to=a) ``` ### Origin sets An `OriginSet` is not a type of origin, it represents a group of origins. Origin sets are used for tracking the lifetimes of values captured in parametric closures. An `OriginSet` **isn't** a general-purpose mechanism for expressing a combination of multiple origins. Instead, you can use `origin_of()` to express an [origin union](#origin-unions). ### Origin values Most origin values are created by the compiler. As a developer, there are a few ways to specify origin values: - Static origin. The `ImmStaticOrigin` `comptime` value represents immutable values that last for the duration of the program. String literal values have a `ImmStaticOrigin`. - Derived origin. The `origin_of()` magic function returns the origin associated with the value (or values) passed in. - Inferred origin. You can use inferred parameters to capture the origin of a value passed in to a function. - Untracked origins. The untracked origins, `MutUntrackedOrigin` and `ImmUntrackedOrigin` represent values that are not tracked by the lifetime checker, such as dynamically-allocated memory. - Wildcard origins. The `ImmUnsafeAnyOrigin` and `MutUnsafeAnyOrigin` `comptime` values are special cases indicating a reference that might access any live value. #### Static origins You can use the static origin `ImmStaticOrigin` when you have a value that exists for the entire duration of the program. For example, the `StringLiteral` method [`as_string_slice()`](/docs/std/builtin/string_literal/StringLiteral/#as_string_slice) returns a [`StringSpan`](/docs/std/collections/string/string_span/StringSpan/) pointing to the original string literal. String literals are static—they're allocated at compile time and never destroyed—so the slice is created with an immutable, static origin. #### Derived origins Use the `origin_of(value)` operator to obtain a value's origin. An argument to `origin_of()` can take an arbitrary expression that yields one of the following: - An origin value. - A value with a memory location. For example: ```mojo origin_of(self) origin_of(x.y) origin_of(foo()) ``` The `origin_of()` operator is analyzed statically at compile time; The expressions passed to `origin_of()` are never evaluated. (For example, when the compiler analyzes `origin_of(foo())`, it doesn't run the `foo()` function.) The following struct stores a string value using a [`OwnedPointer`](/docs/std/memory/owned_pointer/OwnedPointer/): a smart pointer that holds an owned value. The `as_ptr()` method returns a `Pointer` to the stored string, using the same origin as the original `OwnedPointer`. ```mojo from std.memory import OwnedPointer, Pointer struct BoxedString: var o_ptr: OwnedPointer[String] def __init__(out self, value: String): self.o_ptr = OwnedPointer(value) def as_ptr(mut self) -> Pointer[String, origin_of(self.o_ptr)]: return Pointer(to=self.o_ptr[]) ``` Note that the `as_ptr()` method takes its `self` argument as `mut self`. If it used the default argument convention, it would be immutable, and the derived origin (`origin_of(self.o_ptr)`) would also be immutable. You can also pass multiple expressions to `origin_of()` to express the union of two or more origins: `origin_of(a, b)` #### Origin unions When a function returns a reference or pointer that can have one of several different origins, you can express the referenced origin as a union of all of the possible origin values. The union of two or more origins creates a new origin that references all of the original origins for the purposes of lifetime extension (so a union of the origins of `a` and `b` extends both lifetimes). An origin union is mutable if and only if all of its constituent origins are mutable. Use an origin union For an example, see [Return values with union origins](#return-values-with-union-origins). #### Inferred origins Since origins are parameters, the compiler can *infer* an origin value from the argument passed to a function or method, as described in [Parameter inference](/docs/manual/parameters/#parameter-inference). This allows a function to return a value that has the same origin as the argument passed to it. See the section on [`ref` arguments](#ref-arguments) for an example using an inferred origin. #### Untracked origins The untracked origins, `MutUntrackedOrigin` and `ImmUntrackedOrigin` represent values that do not alias any existing value. That is, they point to memory that is not owned by any other variable, and are therefore not tracked by the lifetime checker. For example, the [`alloc()`](/docs/std/memory/alloc/alloc/) function returns an `Allocation` for a new dynamically-allocated block of memory, with the origin `MutUntrackedOrigin`. The origin indicates that the memory is not managed by the Mojo ownership system. When you use an unsafe API like this, you're responsible for managing the lifetime yourself: for example, a struct that allocates memory should generally free that memory in its deinitializer. #### Wildcard origins The wildcard origins, `ImmUnsafeAnyOrigin` and `MutUnsafeAnyOrigin`, are special cases indicating a reference that might access any live value. These were previously widely used for unsafe pointers. Using a pointer with a wildcard origin into a scope effectively disables Mojo's ASAP destruction for any values in that scope, as long as the pointer is live. It also prevents Mojo from enforcing [argument exclusivity](/docs/manual/values/ownership/#argument-exclusivity) and hides unused variable warnings. Accordingly, the use of wildcard origins is discouraged, and should be used as a last resort. ## Working with references You can use the `ref` keyword with arguments and return values to specify a reference with parametric mutability. That is, they can be either mutable or immutable. A `ref` return value looks like any other return value to the calling function, but it's a *reference* to an existing value, not a copy. ### `ref` arguments The `ref` argument convention lets you specify an argument of parametric mutability: that is, you don't need to know in advance whether the passed argument will be mutable or immutable. There are several reasons you might want to use a `ref` argument: - You want to accept an argument with parametric mutability. - You want to tie the lifetime of one argument to the lifetime of another argument. - When you want an argument that is guaranteed to be passed in memory: this can be useful for parameterized arguments that need an identity, whether or not the concrete type is register passable. The syntax for a `ref` argument is: ref arg_name: arg_type Or: ref[origin_specifier(s)] arg_name: arg_type In the first form, the origin and mutability of the `ref` argument is inferred from the value passed in. The second form includes an origin clause, consisting of one or more origin specifiers inside square brackets. An origin specifier can be either: - An origin value. - An arbitrary expression, which is treated as shorthand for `origin_of(expression)`. In other words, the following declarations are equivalent: ```mojo ref[origin_of(self)] ref[self] ``` - An [`AddressSpace`](/docs/std/memory/address_space/AddressSpace/) value. - An underscore character (`_`) to indicate that the origin is *unbound*. This is equivalent to omitting the origin specifier. ```mojo def add_ref(ref a: Int, b: Int) -> Int: return a+b ``` You can also name the origin explicitly. This is useful if you want to restrict the argument to either a `ImmOrigin` or `MutOrigin`, or if you want to bind a function's return value to the origin of an argument. For example, the `Span` type is a non-owning view of contiguous data (like a substring of a string, or a subset of a list). Because it points to data that it doesn't own, it is parameterized on an origin value that represents the lifetime and ownership of the data it points to. In the following example, the `to_byte_span()` function takes a `List[Byte]` and returns a `Span[Byte]` with the same origin as the list: ```mojo from std.collections import List, Span def to_byte_span[ is_mutable: Bool, //, origin: Origin[mut=is_mutable], ](ref[origin] list: List[Byte]) -> Span[Byte, origin]: return Span(list) def main(): var list: List[Byte] = [77, 111, 106, 111] _ = to_byte_span(list) ``` In this example, the `origin` parameter is inferred from the `list` argument, and then used as the origin for the returned `Span`. Since the `Span` takes on the origin of the `list` argument, the Mojo compiler can identify the span's data as owned by the list. The span will have the same lifetime as the list, and the span will be mutable if the list is mutable. ### `ref` return values Like `ref` arguments, `ref` return values allow a function to return a mutable or immutable reference to a value. The syntax for a `ref` return value is: -> ref[origin_specifier(s)] arg_type Note that you **must** provide an origin specifier for a `ref` return value. The values allowed for origin specifiers are the same as the ones listed for [`ref` arguments](#ref-arguments). `ref` return values can be an efficient way to handle updating items in a collection. The standard way to do this is by implementing the `__getitem__()` and `__setitem__()` dunder methods. These are invoked to read from and write to a subscripted item in a collection: ```mojo var value = list[a] list[b] += 10 ``` With a `ref` argument, `__getitem__()` can return a mutable reference that can be modified directly. This has pros and cons compared to using a `__setitem__()` method: - The mutable reference is more efficient—a single update isn't broken up across two methods. However, the referenced value must be in memory. - A `__getitem__()`/`__setitem__()` pair allows for arbitrary code to be run when values are retrieved and set. For example, `__setitem__()` can validate or constrain input values. For example, in the following example, `NameList` has a `__getitem__()` method that returns a reference: ```mojo struct NameList: var names: List[String] def __init__(out self, *names: String): self.names = [] for name in names: self.names.append(name) def __getitem__(ref self, index: Int) raises -> ref[self.names[0]] String: if (index >=0 and index < len(self.names)): return self.names[index] else: raise Error("index out of bounds") def main() raises: var list = NameList("Thor", "Athena", "Dana", "Vrinda") ref name = list[2] print(name) name += "?" print(list[2]) ``` ```output Dana Dana? ``` Note the use of the `ref name` syntax to create a reference binding. If you assign a `ref` return value to a variable, the variable receives a *copy* of the referenced item. Use a [reference binding](/docs/manual/variables/#reference-bindings) if you need to capture the reference for future use: ```mojo var name_copy = list[2] # owned copy of list[2] ref name_ref = list[2] # reference to list[2] ``` #### Parametric mutability of return values Another advantage of `ref` return arguments is the ability to support parametric mutability. For example, recall the signature of the `__getitem__()` method above: ```mojo def __getitem__(ref self, index: Int) raises -> ref[self] String: ``` Since the `origin` of the return value is tied to the origin of `self`, the returned reference will be mutable if the method was called using a mutable reference. The method still works if you have an immutable reference to the `NameList`, but it returns an immutable reference: ```mojo def pass_immutable_list(list: NameList) raises: print(list[2]) # list[2] += "?" # Error, this list is immutable def main() raises: var list = NameList("Sophie", "Jack", "Diana") pass_immutable_list(list) ``` ```output Diana ``` Without parametric mutability, you'd need to write two versions of `__getitem__()`, one that accepts an immutable `self` and another that accepts a mutable `self`. #### Return values with union origins A `ref` return value can include multiple values in its origin specifier, which yields the union of the origins. For example, the following `pick_one()` function returns a reference to one of the two input strings, with an origin that's a union of both origins. ```mojo def pick_one(cond: Bool, ref a: String, ref b: String) -> ref[a, b] String: return a if cond else b ``` Because the compiler can't statically determine which branch will be picked, this function must use the union origin `[a, b]`. This ensures that the compiler extends the lifetime of *both* values as long as the returned reference is live. The returned reference is mutable if **both** `a` and `b` are mutable. --- ## Ownership A challenge you might face when using some programming languages is that you must manually allocate and deallocate memory. When multiple parts of the program need access to the same memory, it becomes difficult to keep track of who "owns" a value and determine when is the right time to deallocate it. If you make a mistake, it may result in a "use-after-free" error, a "double free" error, or a "leaked memory" error, any one of which can be catastrophic. Mojo helps avoid these errors by ensuring there is only one variable that owns each value at a time, while still allowing you to share references with other functions. When the life span of the owner ends, Mojo [destroys the value](/docs/manual/lifecycle/death). Programmers are still responsible for making sure any type that allocates resources (including memory) also deallocates those resources in its deinitializer. Mojo's ownership system ensures that deinitializers are called promptly. On this page, we'll explain the rules that govern this ownership model, and how to specify different argument conventions that define how values are passed into functions. ## Ownership summary The fundamental rules that make Mojo's ownership model work are the following: - Every value has only one owner at a time. - When the lifetime of the owner ends, Mojo destroys the value. - If there are existing references to a value, Mojo extends the lifetime of the owner. ### Variables and references A variable *owns* its value. A struct owns its fields. A *reference* allows you to access a value owned by another variable. A reference has either mutable access or immutable access to that value. Mojo references are created when you call a function: function arguments are passed as mutable or immutable references. A function can return a reference instead of returning a value. To capture a returned reference, you can use a reference binding: ```mojo ref value_ref = list[0] ``` ## Argument conventions In all programming languages, code quality and performance is heavily dependent upon how functions treat argument values. That is, whether a value received by a function is a unique value or a reference, and whether it's mutable or immutable, has a series of consequences that define the readability, performance, and safety of the language. In Mojo, we want to provide full [value semantics](/docs/manual/values/value-semantics) by default, which provides consistent and predictable behavior. But as a systems programming language, we also need to offer full control over memory optimizations, which generally requires reference semantics. The trick is to introduce reference semantics in a way that ensures all code is memory safe by tracking the lifetime of every value and destroying each one at the right time (and only once). All of this is made possible in Mojo through the use of argument conventions that ensure every value has only one owner at a time. An argument convention specifies whether an argument is mutable or immutable, and whether the function owns the value. Each convention is defined by a keyword at the beginning of an argument declaration: - default: The function receives an **immutable reference**. This means the function can read the original value (it's *not* a copy), but it can't mutate (modify) it. - `mut`: The function receives a **mutable reference**. This means the function can read and mutate the original value (it's *not* a copy). - `var`: The function takes **ownership** of a value. This means the function has exclusive ownership of the argument. The caller might choose to transfer ownership of an existing value to this function, but that's not always what happens. The callee might receive a newly-created value, or a copy of an existing value. - `ref`: The function gets a reference with a parametric mutability: that is, it follows the mutability of the referenced value. `ref` arguments are an advanced topic, and they're described in more detail in [Lifetimes, origins, and references](/docs/manual/values/lifetimes/). - `out`: A special convention used for the `self` argument in [initializers](/docs/manual/lifecycle/life/#constructor) and for [named results](/docs/manual/functions/#named-results). An `out` argument is uninitialized at the beginning of the function, and must be initialized before the function returns. Although `out` arguments show up in the argument list, they're never passed in by the caller. - `deinit`: A special convention used in the deinitializer and consuming-move lifecycle methods. A `deinit` argument is initialized at the beginning of the function, and uninitialized when the function returns. For example, this function has one argument that's a mutable reference and one that's immutable: ```mojo def add(mut x: Int, y: Int): x += y def main(): var a = 1 var b = 2 add(a, b) print(a) # 3 ``` You've probably already seen some function arguments that don't declare a convention. By default, all arguments use the default convention of an immutable read-only reference. In the following sections, we'll explain each of these conventions in more detail. ### Deinitializing arguments (`deinit`) The `deinit` convention isn't limited to `self`. You can write methods and functions that destruct other instances: ```mojo struct Pair: def destroy_other(self, deinit other: Self): # Can take from fields of `other` here ``` Like `deinit self`, the `deinit` convention in this example tells the compiler that `other` is tagged for destruction. Using `deinit` means `other` is logically deinitialized at the end of the method. Because of this, it's safe to move values out of `other`, since the instance's lifetime is guaranteed to complete. ## Immutable arguments (default) The default convention is an immutable read-only reference. The callee receives an immutable reference to the argument value. For example: ```mojo def print_list(list: List[Int]): print(list.__str__()) def main(): var values: List[Int] = [1, 2, 3, 4] print_list(values) ``` ```output [1, 2, 3, 4] ``` Here the `print_list()` function can read from the `list` argument, but not mutate it. `list` is a reference to `values` in the `main()` function, not a copy. In general, passing an immutable reference is much more efficient when handling large or expensive-to-copy values, because the copy initializer and deinitializer aren't invoked for a default (immutable reference) argument. ### Compared to C++ and Rust Mojo's default argument convention is similar in some ways to passing an argument by `const&` in C++, which also avoids a copy of the value and disables mutability in the callee. However, the default convention differs from `const&` in C++ in two important ways: - The Mojo compiler implements a lifetime checker that ensures that values are not destroyed when there are outstanding references to those values. - Small values like `Int`, `Float`, and `SIMD` are always passed in machine registers. This provides a significant performance enhancement compared to languages like C++ and Rust. The major difference between Rust and Mojo is that Mojo doesn't require a sigil on the caller side to pass by immutable reference. Also, Mojo is more efficient when passing small values, and Rust defaults to moving values instead of passing them around as a read-only reference. These policy and syntax decisions allow Mojo to provide an easier-to-use programming model. ## Mutable arguments (`mut`) If you'd like your function to receive a **mutable reference**, add the `mut` keyword in front of the argument name. You can think of `mut` like this: it means any changes to the value *in*side the function are visible *out*side the function. For example, this `mutate()` function updates the original `list` value: ```mojo def print_list(list: List[Int]): print(list.__str__()) def mutate(mut l: List[Int]): l.append(5) def main(): var values: List[Int] = [1, 2, 3, 4] mutate(values) print_list(values) ``` ```output [1, 2, 3, 4, 5] ``` That behaves like an optimized replacement for this: ```mojo def print_list(list: List[Int]): print(list.__str__()) def mutate_copy(l: List[Int]) -> List[Int]: # def creates an implicit copy of the list because it's mutated l.append(5) return l def main(): var values: List[Int] = [1, 2, 3, 4] values = mutate_copy(values) print_list(values) ``` ```output [1, 2, 3, 4, 5] ``` Although the code using `mut` isn't that much shorter, it's more memory efficient because it doesn't make a copy of the value. However, remember that the values passed as `mut` must already be mutable. For example, if you try to take an immutable reference and pass it to another function as `mut`, you'll get a compiler error because Mojo can't form a mutable reference from an immutable reference. :::note You can't define [default values](/docs/manual/functions#optional-arguments) for `mut` arguments. ::: ### Argument exclusivity Mojo enforces *argument exclusivity* for mutable references. This means that if a function receives a mutable reference to a value (such as an `mut` argument), it can't receive any other references to the same value—mutable or immutable. That is, a mutable reference can't have any other references that *alias* it. For example, consider the following code example: ```mojo def append_twice(mut s: String, other: String): # Mojo knows 's' and 'other' can't be the same string. s += other s += other def invalid_access(): var my_string = "o" # Create a run-time String value # error: passing `my_string` mut is invalid since it's also passed # as an immutable reference append_twice(my_string, my_string) print(my_string) ``` This code is confusing because the user might expect the output to be `ooo`, but since the first addition mutates both `s` and `other`, the actual output would be `oooo`. Enforcing exclusivity of mutable references not only prevents coding errors, it also allows the Mojo compiler to optimize code in some cases. One way to avoid this issue when you do need both a mutable and an immutable reference (or need to pass the same value to two arguments) is to make a copy: ```mojo def valid_access(): var my_string = "o" # Create a run-time String value var other_string = my_string # Create a copy of the String value append_twice(my_string, other_string) print(my_string) ``` Note that argument exclusivity isn't enforced for register-passable trivial types (like `Int` and `Bool`) as they're always passed by copy. When passing the same value into two `Int` arguments, the callee receives two copies of the value. ## Transfer arguments (`var` and `^`) If you want your function to take *ownership* of a value, add the `var` keyword before the argument name. This convention is often combined with using the postfix `^` transfer sigil on an argument at the call site. When using a variable, transferring a value leaves the original variable uninitialized. You can't use the variable after the transfer until you assign it a new value of the original type. ### Transferring with `var` `var` behaves differently depending on whether the caller uses the `^` transfer sigil and whether the value conforms to `Copyable`. The `var` keyword doesn't guarantee that the function receives *the original value*. It guarantees only that the function receives *ownership of a value*. That happens in one of three ways: - **Value transfer**: The caller uses the `^` transfer sigil. This transfers the value, leaving the original variable uninitialized. The function argument receives ownership. - **Copying**: Without the transfer sigil, Mojo copies the value. If the type isn't `Copyable`, this produces a compile-time error. - **Newly created value**: The caller passes a newly created value, such as the result of a function call. In this case, no variable owns the value, so ownership transfers directly to the callee. For example: ```mojo def take(var s: String): pass def main(): take("A brand-new String!") ``` The following code works by making a copy of the string, because `take_text()` uses the `var` convention, and the caller doesn't include the transfer sigil: ```mojo def take_text(var text: String): text += "!" print(text) def main(): var message = "Hello" # Create a run-time String value take_text(message) print(message) ``` ```output Hello! Hello ``` However, if you add the `^` transfer sigil when calling `take_text()`, the compiler complains about `print(message)`, because at that point, the `message` variable is no longer initialized. That is, this version doesn't compile: ```mojo def main(): var message = "Hello" # Create a run-time String value take_text(message^) print(message) # error: use of uninitialized value 'message' ``` This is a critical feature of Mojo's lifetime checker, because it ensures that no two variables have ownership of the same value. To fix the error, you must not use the `message` variable after you end its lifetime with the `^` transfer sigil. So here is the corrected code: ```mojo def take_text(var text: String): text += "!" print(text) def main(): var message = "Hello" # Create a run-time String value take_text(message^) ``` ```output Hello! ``` Regardless of how it receives the value, when the function declares an argument as `var`, it's certain that it has unique mutable access to that value. Because the value is owned, the value is destroyed when the function exits—unless the function transfers the value elsewhere. For example, in the following example, `add_to_list()` takes a string and appends it to the list. Ownership of the string is transferred to the list, so it's not destroyed when the function exits. On the other hand, `consume_string()` doesn't transfer its `var` value out, so the value is destroyed at the end of the function. ```mojo def add_to_list(var name: String, mut list: List[String]): list.append(name^) # name is uninitialized, nothing to destroy def consume_string(var s: String): print(s) # s is destroyed here ``` ### Transfer implementation details In Mojo, you shouldn't conflate "ownership transfer" with a "move operation"—these aren't strictly the same thing. There are multiple ways that Mojo transfers ownership of a value: - If a type implements the [move initializer](/docs/manual/lifecycle/life#move-constructor), `__init__(take=)`, Mojo may invoke this method *if* a value of that type is transferred into a function as a `var` argument, *and* the original variable's lifetime ends at the same point (with or without use of the `^` transfer sigil). - In some cases, Mojo optimizes away the move operation entirely, leaving the value in the same memory location but updating its ownership. In these cases, a value transfers without invoking either the copy or move initializers. In order for the `var` convention to work *without* the transfer sigil, the value type must be copyable (via `__init__(out self, *, copy: Self)`). --- ## Value semantics Mojo doesn't enforce value semantics or reference semantics. It supports them both and allows each type to define how it is created, copied, and moved (if at all). So, if you're building your own type, you can implement it to support value semantics, reference semantics, or a bit of both. That said, Mojo is designed with argument behaviors that default to value semantics, and it provides tight controls for reference semantics that avoid memory errors. The controls over reference semantics are provided by the [value ownership model](/docs/manual/values/ownership), but before we get into the syntax and rules for that, it's important that you understand the principles of value semantics. Generally, it means that each variable has unique access to a value, and any code outside the scope of that variable cannot modify its value. ## Intro to value semantics In the most basic situation, sharing a value-semantic type means that you create a copy of the value. This is also known as "pass by value." For example, consider this code: ```mojo def main(): var x = 1 var y = x y += 1 print("x:", x) print("y:", y) ``` ```output x: 1 y: 2 ``` We assigned the value of `x` to `y`, which creates the value for `y` by making a copy of `x`. When we increment `y`, the value of `x` doesn't change. Each variable has exclusive ownership of a value. Whereas, if a type instead uses reference semantics, then `y` would point to the same value as `x`, and incrementing either one would affect the value for both. Neither `x` nor `y` would "own" the value, and any variable would be allowed to reference it and mutate it. Numeric values in Mojo are value semantic because they're trivial types, which are cheap to copy. ## Value semantics in Mojo functions Value semantics also apply to function arguments in Mojo by default. However, the way in which they apply differs depending on the [argument convention](/docs/manual/values/ownership#argument-conventions), which is discussed in the [Ownership](/docs/manual/values/ownership/) page. For example, in the following function, the `y` argument is immutable by default, so if the function wants to modify the value in the local scope, it needs to make a local copy: ```mojo def add_two(y: Int): # y += 2 # This would cause a compiler error because `y` is immutable # We can instead make an explicit copy: var z = y z += 2 print("z:", z) def main(): var x = 1 add_two(x) print("x:", x) ``` ```output z: 3 x: 1 ``` This is all consistent with value semantics because each variable maintains unique ownership of its value. The way the function receives the `y` value is a "look but don't touch" approach to value semantics. This is also a more memory-efficient approach when dealing with memory-intensive arguments, because Mojo doesn't make any copies unless we explicitly make the copies ourselves. Thus, the default behavior for function arguments is fully value semantic: arguments are immutable references, and any living variable from the caller is not affected by the function. But we must also allow reference semantics (mutable references) because it's how we build performant and memory-efficient programs (making copies of everything gets really expensive). The challenge is to introduce reference semantics in a way that does not disturb the predictability and safety of value semantics. The way we do that in Mojo is, instead of enforcing that every variable have "exclusive access" to a value, we ensure that every value has an "exclusive owner," and destroy each value when the lifetime of its owner ends. On the next page about [value ownership](/docs/manual/values/ownership/), you'll learn how to modify the default argument conventions, and safely use reference semantics so every value has only one owner at a time. --- ## Variables A variable is a name that holds a value or object. All variables in Mojo are mutable by default. Their value can change. If you want to define a constant value that can't change at runtime, see the [`comptime` keyword](/docs/manual/metaprogramming/comptime-evaluation/#comptime-values) or pass the value as a non-mutable function argument. When you declare a variable in Mojo, you allocate a logical storage location, and bind a name to that storage. ```mojo var greeting: String = "Hello World" ``` A `var` declaration does three things: - It declares a logical storage location, which is tied to a particular type. In this case, it holds `String` instances. - It binds the name `greeting` to this logical storage location. - It *initializes* the storage space with a newly created `String` value, using "Hello World". The new value is *owned by* the variable. No other variable can own this value unless you transfer its ownership. ## Variable declarations To declare a variable, use `var` with a name. You can give it a value, a type annotation, or both. The more you annotate, the more explicit your code is, and the easier it is to read and maintain: ```mojo var a = 5 # Mojo infers that a is type Int var b: Float64 = 3.14 # Explicit declaration of Float64 type var c: String # The name is created but uninitialized ``` A variable's type never changes. Its storage is strongly typed upon creation and can only hold values of that type: ```mojo var count = 8 # count is type Int count = "Nine?" # Error: can't implicitly convert 'StringLiteral' to 'Int' ``` A variable is scoped to the block in which it is declared. Its value is destroyed at last use. You may transfer a value from a variable so it no longer lives in that variable or that scope. The name, that is, the variable itself, is destroyed when the scope ends. - Variables are names that hold values. - Values are data that live in memory. ## Variable scopes Variables in Mojo use *lexical scoping*. A variable's definition is determined by where it appears in the source code, not when it executes at runtime. The specific scope level depends on how the variable is declared. Variables have **block-level** scope. Nested code can read and modify variables defined in an outer scope. An outer scope can't read variables defined in an inner scope. For example, the `if` code block shown here creates an inner scope where outer variables are accessible to read/write, but any new variables do not live beyond the scope of the `if` block: ```mojo def lexical_scopes(): var num = 1 var dig = 1 if num == 1: print("num:", num) # Reads the outer-scope "num" var num = 2 # Creates new inner-scope "num" print("num:", num) # Reads the inner-scope "num" dig = 2 # Updates the outer-scope "dig" print("num:", num) # Reads the outer-scope "num" print("dig:", dig) # Reads the outer-scope "dig" ``` ```output num: 1 num: 2 num: 1 dig: 2 ``` Note that the `var` statement inside the `if` creates a **new** variable with the same name as the outer variable. This prevents the inner if-statement from accessing the outer `num` variable. This is called "variable shadowing," where the inner scope variable hides or "shadows" a variable from an outer scope. The lifetime of the inner `num` ends exactly where the `if` code block ends, because that's the scope in which the variable was defined. ## Copying and moving values An assignment statement of a newly created value or a literal establishes ownership: ```mojo var owning_variable = "Owned value" ``` An assignment of an existing variable's value transfers ownership of that value or a copy of that value to the new variable: ```mojo var source = String("Hello") var copied = source # A copy var moved = source^ # A transfer ``` The right-hand side variables must be `Copyable` or `Movable` to be assigned in this way. After the assignment the new variable owns a value, whether copied or transferred. A transfer leaves `source` uninitialized, and you can't use it again until you assign it a new value. The value on the right-hand side of the assignment statement must be transferable to the new variable. Here's an example where that doesn't work: ```mojo var first: List[Int] = [1, 2, 3] var second = first # error: 'List[Int]' is not implicitly copyable because # it doesn't conform to 'ImplicitlyCopyable' ``` The first assignment is no problem: the expression `[1, 2, 3]` creates a new `List` value without an owner, so `first` becomes that owner without any ambiguity. The second assignment errors because `first` isn't implicitly copyable and the value isn't transferred. Each outcome depends on type features for the values involved in assignment. - A `Copyable` type can be copied explicitly, by calling its copy initializer or the `copy()` method. ```mojo var second = first.copy() ``` Copying leaves `first` unchanged. `second` is assigned its own, uniquely owned copy of the list. - `ImplicitlyCopyable` types can be copied without an explicit signal: ```mojo var one_value = 15 var another_value = one_value # implicit copy ``` Implicitly copyable types are generally simple value types like `Int`, `Float64`, and `Bool`, which can be copied trivially. - The ownership of a value can be explicitly transferred from one variable to another by appending the *transfer sigil* (`^`) after the value to transfer: ```mojo var second = first^ ``` This moves the value to `second`, and leaves `first` uninitialized. This ownership may move the value from one memory location to another. This requires the value to be `Movable`. ## Reference bindings Some APIs return [_references_](/docs/manual/values/lifetimes/#working-with-references) to values owned elsewhere. References avoid copying values. For example, when you retrieve a value from a collection, the collection returns a reference, instead of a copy: ```mojo var animals: List[String] = ["Cats", "Dogs", "Zebras"] print(animals[2]) # Prints "Zebras", does not copy the value. ``` If you assign a reference to a *variable*, it creates a copy (if the value is implicitly copyable) or produces an error (if it isn't): ```mojo var items: List[Int] = [99, 77, 33, 12] var item = items[1] # item is a copy of items[1] item += 1 # increments item print(items[1]) # prints 77 ``` To name a reference, use the `ref` keyword to create a reference binding: ```mojo ref item_ref = items[1] # item_ref is a reference to item[1] item_ref += 1 # increments items[1] print(items[1]) # prints 78 ``` The name `item_ref` is bound to `items[1]`. All reads and writes to `item_ref` go to the item it references. Reference bindings can't be re-assigned: ```mojo ref item_ref = items[2] # error: invalid redefinition of item_ref ``` For more information on references, see [Working with references](/docs/manual/values/lifetimes/#working-with-references). --- ## How to read the standard library API documentation Standard library declarations use a compact syntax to describe compile-time parameters, runtime arguments, ownership, mutability, and calling conventions. Mojo idioms appear throughout the API reference. ## How does Mojo use "parameter" and "argument"? Many languages use *parameter* and *argument* interchangeably, or distinguish them only as declarations and call sites. Mojo uses these terms differently because neither approach provides enough vocabulary for compile-time and runtime programming. Mojo gives two familiar words more precise meanings: - **Parameter** refers to compile-time entities. - **Argument** refers to runtime values and references. In declarations, compile-time parameters appear in square brackets (`[]`), followed by runtime arguments in parentheses (`()`). Mojo uses one language for both compile-time and runtime programming, rather than separating them into a language plus a macro or template system. ## How is "Self" different from "self"? `Self` (capital S) is a keyword that refers to an enclosing struct or, when used in a trait definition, the type that implements the trait. Within a type or trait definition, `Self.` identifies compile-time parameters and other `comptime` declarations. For example, `Self.T` refers to the parameter `T` defined by the enclosing type. `self` (lowercase) refers to an instance of the enclosing type. Instance methods declare `self` as their first argument. Callers do not pass it explicitly. You write `instance.method()`, not `instance.method(instance)`. Static methods do not have a `self` argument. ## What are the parameter naming conventions? Mojo follows naming conventions used in languages like Rust and C++. Type parameter names use PascalCase, short (`T`, `E`) or descriptive (`ErrorType`, `Element`). By convention, `T`, `U`, `V` are general types; `K`/`V` for key-value pairs; `E` for errors; `H` for hashers. Value parameter names use lower_snake_case and should be descriptive (`capacity`, `hasher`, `tile_x`). ## What do the words before argument names mean? Argument conventions appear before argument names in function declarations. They describe the contract between caller and callee. They indicate both ownership (`var`, `ref`, unmarked) and what the function may do with an argument (`mut`, `out`, `deinit`): {/* markdownlint-disable MD013 */} | Convention | What it means | Example | |--------------|----------------------------------------------------------------------------------------------------------|------------------------------------------------------------| | *(unmarked)* | A reference to an existing value with read-only `imm` access. | `def abs[T: Absable](value: T) -> T` | | `mut` | A reference to an existing value. The function can modify the value if it is mutable. | `def append(mut self, codepoint: Codepoint)` | | `ref` | A reference to an existing value. The function inherits the value's mutability. | `def __getitem__(ref self, idx: Int)` | | `var` | The function owns its own value. The caller keeps the original unless ownership is transferred. | `def insert(mut self, var key: Self.K, var value: Self.V)` | | `out` | An uninitialized slot that the function must initialize before it returns. Used for type initialization. | `def __init__(out self, *, capacity: Int)` | | `deinit` | The function takes ownership and destroys the value. | `def __deinit__(deinit self)` | {/* markdownlint-enable MD013 */} ## What are those symbols in parameter and argument lists? Three markers divide parameter and argument lists into zones that control how callers pass values: | Marker | Arguments | Parameters | |--------|-----------------|-----------------| | `//` | No | Infer-only | | `/` | Positional-only | Positional-only | | `*` | Keyword-only | Keyword-only | - `//` separates infer-only parameters from named parameters. - Everything before `/` is positional-only. Callers must pass these values by position, not by name. - Everything after `*` is keyword-only. Callers must pass these values by name. ## Why do variadics have `*` before some type names? Variadic arguments accept a varying number of values. - `*` before the argument name accepts any number of positional arguments of the same type. - `*` before both the name and the type annotation creates a *variadic pack* that accepts arguments of different types (heterogeneous arguments). ## Why is `def` used as a type? Function pointers and closures use function types that describe their signature. The simplest is `def()`, a function with no arguments and no return value. When used as a type, `def` specifies argument and return types but not argument names. For example, `def(Int, Int) -> Int` is a function that takes two `Int` arguments and returns an `Int`. --- ## Mojo stability guarantees :::caution This is an early and preliminary version of our stability discussion. We'll update this page as we finalize the Mojo 1.x stability model. ::: The Mojo language and standard library follow semantic versioning for language features and standard library APIs identified as stable. Roughly speaking, this means: - Major versions (1.0, 2.0) can contain breaking changes that aren't backward-compatible. - Minor versions (1.1, 1.2) can add new functionality in a backward-compatible way. - Patch versions (1.0.1, 1.2.1) can contain bug fixes that are backward-compatible. Stability guarantees apply to source code only; the Mojo ABI is currently not stable. Unstable features can change at any point. We may make exceptions to the stability policy if we discover a critical issue with a stabilized API. ## Mojo standard library stability We consider standard library APIs unstable unless specifically marked stable. In source code, the @stable(since="version") decorator marks these APIs. The API documentation displays these stable markers: - Stable structs and traits show a "Stable since version" label below the struct/trait name. - Other stable API members show a version badge in the right margin (for example, "1.0.0"). Marking a struct stable means that the struct's *signature* is stable. It **doesn't** guarantee that any member APIs are stable. We stabilize member APIs on a case-by-case basis. There are two limited cases in which we may change stable APIs: - For functions and methods, stabilizing one or more members of an overload set doesn't guarantee those exact members will continue to exist. The exact overload set may evolve, but it will continue to support the same inputs. - We may change a stable struct's signature by adding new optional parameters, with default values that match the previous behavior. Adding a new parameter like this is backward-compatible in most cases. However, this can break code that explicitly unbinds all parameters using the ellipsis (`...`). To understand how this could happen, consider the following code: ```mojo def callee(l: List[Int]): pass def caller(l: List[Int, ...]): callee(l) ``` Since `List` only has one parameter, the ellipsis in `List[Int, ...]` doesn't unbind anything. `List[Int]` and `List[Int, ...]` evaluate to the same type. But suppose `List` adds a new, optional parameter: ```mojo struct List[T: Movable, /, A: Allocator = DefaultAllocator]: ``` In this case, the code above will not compile. The `List[Int, ...]` now unbinds the `A` parameter, so the `caller()` function is automatically parameterized on `A`. It will accept a `List` with *any* value for `A`. But the `callee()` function will *only* accept a `List[Int]` with the *default* value for A. When you invoke Mojo with the `--warn-on-unstable-apis` flag, it issues a warning for each unstable API you use. We don't currently recommend this because the stable API set is small. ## Mojo language stability A wide part of the Mojo language is stable. Most language constructs, including control flow, types, and ownership, are stable. As a rule, Mojo's lifetime and operator dunders such as `__add__()`, `__init__()`, and `__deinit__()` are stable. A small set of language features exists to support the compiler, the standard library, or advanced metaprogramming. Treat these as implementation details, not public-facing language items. They may change or disappear without notice and don't have stability guarantees. Unlike standard library APIs, these language features *can't be marked* as stable or unstable and the compiler won't warn when you use them (`--warn-on-unstable-apis`). When writing code intended for compatibility across future versions of Mojo, avoid relying on these implementation details. ### The double-underscore prefix Avoid using any language feature with a leading double underscore (`__`) unless the manual explicitly documents it as stable. Examples of unsafe prefixed keywords include: - `__mlir_type` - `__mlir_op` - `__mlir_attr` - `__generator_type` These features are subject to change as the language evolves. ### Internal-use dunder names (double underscore prefix and suffix) Several language features exist for specialized compiler or library work. These aren't widely used and may be subject to change. The following are rarely-used power features that are not yet stabilized: - `__merge_with__()` - `__list_literal__` - `__literal_size__` ### Internal-use decorators Some decorators exist only to support language migration, compiler implementation, or standard library development. Avoid decorators named like: - `@__parameter`, `@__copy_capture`: legacy closure support - `@__allow_legacy_custom_self_types` - `@__name` - `@__llvm_arg_metadata` - `@__unsafe_nested_origins_read_only` Consider any decorators beginning with `@__` as internal and unstable, unless the manual explicitly documents them as public. ### `async`/`await` is unstable Lastly, Mojo's async system isn't fully built out. So although the `async` and `await` keywords aren't prefixed, consider them unstable as well. Any async behavior may be subject to change. --- ## Mojo basics cheat sheet Sheet, Panel, } from '@site/src/components/CheatSheets/sheet'; {/* markdownlint-disable MD033 MD034 */} ```mojo def main(): print("Hello, Mojo!") ``` Run it: **mojo hello.mojo**. Every program starts at **main()**. ```mojo # Line comment def greet(): """Docstring: what greet does.""" print("hi") ``` ```mojo var count = 0 # owned, inferred Int var name: String = "Mojo" count = count + 1 # var is mutable var data: List[Int] = [1, 2, 3] ref view = data[0] # ref to an element, no copy view = 99 # writes through to data comptime PI = 3.14159 # compile-time const ``` **var** declares an owned value, mutable by default. **ref** is a reference to a value it doesn't own. Mutable changes update the original value. ```mojo Float32 == Scalar[DType.float32] == SIMD[DType.float32, 1] ``` | Type | Meaning | |--------------|----------------------------------------------------------------------------------------------------| | Int | machine-word integer; default index type | | UInt | machine-word | | Int8 … Int64 | sized integers | | Float64 | default floating point (also Float32, Float16, and special-purpose 8-bit FP8 and 4-bit FP4 floats) | | Bool | True / False | | String | UTF-8, supports Unicode graphemes | | List[T] | growable, homogeneous sequence | | SIMD[dt, n] | n-wide numeric vector | Numeric types are **SIMD** vectors under the hood: scaling to vector math is built in. No implicit numeric conversion: cast explicitly with **Float64(n)**, **Int(x)**, **String(v)**. Use **.cast** for SIMD vectors. Types are **PascalCase** (**Int**); names are **lower_snake_case**. | Op | Meaning | |-----------------------|----------------------------------| | + - * / | add, subtract, multiply, divide | | // % | floor divide, modulo | | ** | power (2 ** 10), also pow(2, 10) | | == != | equal, not equal | | < <= > >= | comparisons (chainable) | | and or not | logical, short-circuit | | += -= | compound assign (\*=, /=, …) | ```mojo a < b < c # chains to (a < b) and (b < c) ``` ```mojo var who = "Mojo" print("Hi, " + who) # concatenation print(t"Hi, {who}!") # interpolation print(1, 2, 3, end=": ") # keyword args var s = String(t"x = {1 + 1}") # to String var raw = r"C:\path" # raw string ``` **print** takes a t-string directly; cast with **String(...)** to use one elsewhere. Triple quotes make multi-line strings. ```mojo if x > 0: print("positive") elif x == 0: print("zero") else: print("negative") # ternary var kind = "even" if x % 2 == 0 else "odd" ``` ```mojo for i in range(5): # 0 1 2 3 4 print(i) for item in [10, 20, 30]: # iterate an array if item == 20: continue # skip to next if item == 30: break # stop the loop print(item) while n > 0: # loop while true print(n) n -= 1 ``` Repeat n times with **for \_ in range(n)** (**\_** discards the value). ```mojo def add(a: Int, b: Int) -> Int: return a + b def greet(name: String = "world"): # default print(t"Hi, {name}") def risky() raises: # may raise raise Error("boom") def nothing(): pass # do-nothing body ``` No `->` means the function returns **None**. `def` can raise only if marked **raises**, so callers can see it coming. ```mojo from std.math import sqrt # one name from std.math import sqrt as root # aliased ``` Built-ins like **Int**, **String**, **List**, and **print** are in the Mojo prelude, no import needed. ```mojo var xs: List[Int] = [1, 2, 3] xs.append(4) print(xs[0]) # 1 print(len(xs)) # 4 ``` Always annotate **List[T]**: a bare bracket literal infers a fixed-size **Array**, which has no **append**. ```mojo @fieldwise_init # synthesizes __init__ struct Point: var x: Int var y: Int struct Counter: var n: Int def __init__(out self): # builds self self.n = 0 def bump(mut self): # modifies self self.n += 1 def main(): var p = Point(3, 4) print(p.x, p.y) # 3 4 ``` Every instance method takes **self** as its first argument. The **out** convention returns the initialized **self** without a return arrow. Structs also support **comptime** constants and static methods (**@staticmethod**). ```mojo def risky() raises: raise Error("boom") def main(): try: risky() except e: print("caught:", e) ``` Mark a raising function **raises**; **raise** signals, **try**/**except** catches. **except e** binds the error. - Every value has a fixed type. No implicit numeric conversion: write `Float64(n)`, `Int(x)`. - Assignment copies the value; it is not a shared reference. - String interpolation is `t"…"`, not `f"…"`. - Declare with **var** (and **ref**) rather than a bare `x = 5`. - No **match** yet: use `if/elif/else`. - A `def` with no `->` returns **None**, same as Python. - Python-style syntax: indentation and `def`, no braces, semicolons, or headers. - Value semantics with moves: `^` transfers ownership (like `std::move` or a Rust move); `__deinit__` gives RAII cleanup. - Behavior comes from **traits**, not class inheritance or templates (like Rust traits or C++20 concepts). - No `?:` ternary or Elvis operator: write `a if cond else b`. - No **switch** yet: use `if/elif/else`. --- ## Mojo compile-time cheat sheet Sheet, Panel, } from '@site/src/components/CheatSheets/sheet'; {/* markdownlint-disable MD033 MD034 */} - One language. Compile time and runtime use the same Mojo. - The compiler acts on what it can prove, using constraints and conformance rules in code. - Proven facts enable or disable methods, constructors, and conformances. - Compile-time computation shifts expensive work out of runtime. ```mojo def repeat[T: Copyable, n: Int](x: T) -> Array[T, n]: ... # T is a type, n is a value struct Matrix[dtype: DType, rows: Int, cols: Int]: ... ``` Use parameterized declarations for functions and structs. The compiler creates a concrete implementation for each unique set of parameter values. ```mojo def sort(mut self) where conforms_to(Self.T, Comparable): ... struct Box[T](Writable where conforms_to(T, Writable)): ... def chunk[w: Int]() where w.is_power_of_two(): ... ``` A precondition the compiler must prove, such as trait facts, numeric truths, or a DType's kind, before the call compiles. ```mojo def largest[T: Comparable & Copyable](xs: List[T]) -> T: ... # only operations T guarantees will compile ``` A trait conformance guarantees what capabilities the parameters support, so only valid code compiles. ```mojo struct Buffer[T: Copyable, n: Int]( Writable where conforms_to(T, Writable), # conforms only if T does ): def first(self) -> Self.T where Self.n > 0: ... # method only if n > 0 ``` A type, method, conformance, or comptime declaration is available only when the compiler can prove its condition. The API is correct by construction: a missing capability or unmet constraint means calls with invalid parameters won't compile. ```mojo comptime name = reflect[T].name() # also .field_count(), .field_names(), ... comptime t = type_of(x) # the type of an expression ``` `reflect[T]` reads a type's structure and `type_of` an expression's type, so parameterized code adapts to any shape. ```mojo def meters(ft: Float64) -> Float64: return ft * 0.3048 comptime track = meters(100.0) # runs while compiling, baked in ``` Every fact must be established at compile time. ```mojo comptime MAX = 2 ** 200 # arbitrary-precision integer comptime c = 0.1 + 0.2 # 0.3 exactly: a literal, kept exact var r = 0.1 + 0.2 # a Float64, subject to rounding ``` Literals stay exact. `Float64` rounds values like `0.1`, and repeated computations accumulate rounding error. Compute accuracy-sensitive constants at compile time. ```mojo def slow_calc() -> Float64: ... # an expensive calculation comptime FACTOR = slow_calc() # computed while compiling, baked in ``` Run expensive computation once while compiling; the result is baked in, free at runtime. For tables and other compile-time data, `global_constant` gives O(1) access without materializing them each time. ```mojo comptime if is_nvidia_gpu(): # only the live branch compiles use_nvidia() else: use_fallback() comptime for i in range(4): # fully unrolled process[i]() ``` `comptime if` compiles the live branch only; `comptime for` unrolls, removing loop overhead. ```mojo comptime w = simd_width_of[DType.float32]() # lanes that fit a register size_of[T]() align_of[T]() # layout, at compile time ``` `sys.info` answers machine questions at compile time, so one source adapts to every target. ```mojo comptime table: List[Int] = [3, 5, 7, 11, 13] # a comptime List (heap-backed) var t = materialize[table]() # -> a runtime List; you choose when it allocates ref g = global_constant[POWERS]() # POWERS: a fixed scalar table, read g[i], no copy ``` Scalars materialize automatically; heap-backed values (`List`, `Dict`) need `materialize`. `global_constant` keeps one static copy to index. ```mojo struct Stack[T: Copyable]: comptime Element = Self.T # associated type comptime capacity = 1024 # comptime value member comptime Scalar[dt: DType] = SIMD[dt, 1] # parametric alias ``` A type carries its own compile-time members (values, associated types, and parametric aliases), reached through `Self`. ```mojo @always_inline def lerp(a: Float64, b: Float64, t: Float64) -> Float64: return a + (b - a) * t # expanded at every call site @no_inline def cold_path(): ... # kept as a real call ``` Inlining replaces a function call with the function body, reducing call overhead for small, frequently called functions. Add `@always_inline` to request inlining, `@no_inline` to exclude the option, or let the compiler decide. - Everything used in compile-time code must be known at compile time. A compile-time value, parameter, `if`, or `for` can't depend on runtime input. - At compile time, you can't perform file I/O, make foreign calls, or call functions that can raise. - Compile-time code runs on the CPU, like all compilation. --- ## Mojo conversions cheat sheet Sheet, Panel, Intro, } from '@site/src/components/CheatSheets/sheet'; {/* markdownlint-disable MD033 MD034 */} ## Numbers make ```mojo 42 # IntLiteral Int(x) # any Intable: Bool, Int8 … Int(s) # any Intable that's raising: String Int(Int32(5)) # any numeric scalar, cross-dtype ``` convert ```mojo Float64(i), UInt(i) # to float, to unsigned (same bits) String(i), Bool(i) # to text, True if non-zero i.cast[DType.int8]() # to another dtype ``` Float to Int truncates toward zero; cross-dtype casts wrap (two's complement). **Int** is **Scalar[DType.int]**, so **.cast** works like any SIMD scalar. **Int** and **UInt** share bits: Int(-1) is UInt 2^64-1. make ```mojo 3.14 # FloatLiteral Float64(x) # any Floatable: Int, Bool Float64(s) # any Floatable that's raising: String Float32(x).cast[DType.float64]() # widen a scalar ``` convert ```mojo Int(f) # truncates toward zero f.cast[DType.float32]() # narrow (precision loss) Bool(f), String(f) # True if non-zero, to text ``` No bare **Float**, and no Float128/256. Convert all other floats (including special purpose) with **.cast()**. make ```mojo Bool(x) # any Boolable ``` convert ```mojo Int(b), Float64(b), String(b) # 0 or 1, 0.0 or 1.0, "True" or "False" ``` truthy — for if / while / and / or / Bool() | Kind | Truthy when | |---------------------------|-----------------------| | **Numbers** | non-zero | | **Strings & collections** | non-empty | | **Optional** | None False, else True | | **PythonObject** | Python's own rules | Any type with a **\_\_bool\_\_** is truthy. Converting a **Bool** to a number is explicit: **Int(b)**, never implicit. make ```mojo SIMD[T, N](x) # splat one value to all lanes SIMD[T, 4](a, b, c, d) # per-lane ``` convert ```mojo v.cast[DType.x]() # new dtype, same lane count SIMD[T, N](scalar) # splat a Scalar up to N lanes ``` **N** is the lane count, not bit width; a lane's bit width is its **DType**. Int, Float64, Int8 … are all SIMD scalars. ## Text make ```mojo String(x) # any StringSpan, StringLiteral, Writable (Int, Float64, Bool …) String(t"{x}") # Not needed for print() String(from_utf8=bytes) # raises on bad UTF-8 String(from_utf8_lossy=bytes) # replaces bad bytes ``` convert ```mojo Int(s) # base-10 parse; raises on "3.5", "0xff", "" Float64(s) # parse (1e3, inf ok); raises "", garbage Bool(s) # True if non-empty ``` access (by byte / codepoint / grapheme) ```mojo s[byte=i], s[byte=i:j] # also for codepoint and single index grapheme s.as_bytes(), s.codepoints(), s.graphemes() # iterators ``` ## Pointers Use **unsafe_ptr()** to access: **List**, **String**, **StringSpan**, **Array**, and **Span**. access through a pointer ```mojo buf.unsafe_ptr() # -> Pointer[T] p[unsafe_offset=i] # deref one element p.unsafe_offset(i)[] # pointer arithmetic, then deref p.unsafe_load[width=N]() # read N lanes -> SIMD[T, N] ``` vectorize a buffer (the escape hatch) ```mojo var v = data.unsafe_ptr().unsafe_load[width=8]() # 8 elements -> one SIMD var total = v.reduce_add() # SIMD-wide reduce ``` **Pointer** is non-null by design. Use **OptionalPointer** for a nullable pointer. Unsafe operations carry the **unsafe\_** prefix or an **unsafe\_** keyword. ## Collections make ```mojo var x: List[T] = [a, b, c] # annotate: unannotated defaults to Array List[T](capacity=n) # empty; initial room for n List[T](length=n, fill=x) # n copies of x (T: Copyable) List(range(n)) # materialize a range List(iterable) # from any iterator / iterable ``` access ```mojo list[i], list[i:j] # element by ref, Span view (no copy) list.unsafe_take_allocation() # hand off the buffer as an Allocation[T] ``` make ```mojo Dict[K, V]() # empty; fill with d[k] = v Dict[K, V](capacity=n) # empty; initial room for n Dict.fromkeys(keys, v) # every key maps to v ``` access ```mojo d.setdefault(key, default) # ref; inserts default if absent d.get(key) d.find(key) # Optional[V] d.keys() d.values() # lazy iterators d.items() # iterator of DictEntry (.key / .value) d.pop(key) # value, removes it ``` make ```mojo Optional(x) # from a value (T inferred from x) Optional[T](), Optional[T](None) # empty ``` access ```mojo o.value(), o.take(), o[] # ref, move out, ref (abort, abort, raise) o.or_else(default) # value, or default ``` --- ## Mojo cheat sheets Quick-reference cards for Mojo syntax and types. --- ## Mojo ownership cheat sheet Sheet, Panel, Yes, No, Partial, } from '@site/src/components/CheatSheets/sheet'; {/* markdownlint-disable MD033 MD034 */} Value ownership is fundamental to Mojo. Every value has exactly one owner, and how values move between owners runs through the whole language. You encounter ownership in two situations: **variables** and **function calls**. Variables can own or reference a value. **Argument conventions** describe how a function uses a value: read-only, reference, mutable, owned, produced, or consumed. ```mojo var data: List[Int] = [1, 2, 3] # owns the list ref view = data[0] # a 2nd name, no copy view = 9 # writes through to data print(data) # [9, 2, 3] ``` **var** always means "I own this." **ref** means "this is a view into someone else's value." A struct's **var** field owns its value and a struct type owns its fields. A **var** assignment uses the right-hand side's policy: it determines whether the value is materialized, constructed, copied, or transferred. A call or expression returning a value constructs; one returning a reference copies out the referenced value. | You write | The var takes ownership of | |---------------------------------------|---------------------------------| | 5.0 / "Hello" / [1, 2, 3] (a literal) | a materialized literal | | SomeType() | a freshly constructed value | | some_value (ImplicitlyCopyable) | an implicit copy | | some_value.copy() (Copyable) | an explicit copy | | some_value^ (Movable) | the source's value, transferred | | some_ref | a copy of the referenced value | A copy doesn't change a value's ownership. Only **^** moves the value to a new owner. | Convention | Meaning | |-------------|------------------------------| | self | imm (immutable) | | mut self | modify the instance | | out self | build it (in \_\_init\_\_()) | | deinit self | destroy the instance | | ref self | parametric mutability | ```mojo def exclaim(var s: String): s += "!" print(s) var g = "Hello" exclaim(g) # copy: g still usable exclaim(g^) # transfer: g uninitialized # print(g) # error: used after transfer ``` The **var** argument takes ownership of the original only with **^**; a plain call implicitly copies (**String** is **ImplicitlyCopyable**), so **g** stays usable. Either value, the copy or the transferred original, ends its lifetime after the **print** (its last use). The same **^** drains a collection in a loop: **for var x in items^** moves each element out. | You write | Into a var arg | |-------------|---------------------------------------------| | f(x) | implicit copy (**ImplicitlyCopyable** only) | | f(x.copy()) | explicit copy | | f(x^) | transfer; x uninitialized after | A borrowing argument (**imm**, **mut**, **ref**) has no **^** lever: you write **f(x)**, and it views the value in place. ```mojo def first[T: Movable](ref xs: List[T]) -> ref[xs[0]] T: return xs[0] ref x = first(xs) # len(xs) known to be > 0 ``` A **ref** return carries an **origin** so the compiler tracks where it points, whether it stays valid, and whether access is mutable. Values are destroyed at last use; a live **ref** keeps the value it refers to alive. | Convention | Owns it? | Mutable? | Caller keeps it? | Reach for it when | |------------|------------------------------------|-------------------------------|-------------------------------------|---------------------------------------------------| | (imm) | | | | reading a value without changing it (the default) | | mut | | | | changing the caller's value in place | | var | (own copy) | | yes, unless ^ | you need a local, mutable copy | | out | (becomes the value) | | it is the result | returning by name instead of -> | | deinit | (consumes) | | | destructors and the source of a move | | ref | (refers) | parametric | | returning or holding a reference with an origin | A convention sits before the argument name: **def f(mut x: Int)**. With no convention, an argument is a read-only borrow: a view into a value you don't own. **mut** makes it a writable view. ```mojo var i = 5 # Int, machine width (default) var i32: Int32 = 5 # SIMD[DType.int32, 1] ``` Literals are produced by the lexer, not built by a constructor. Each compile-time type (**IntLiteral**, **FloatLiteral**, **StringLiteral**) materializes into a runtime value. By default, integer literals are **Int**, floats are **Float64**, and strings are **String**. Use type annotations for specific types like **Byte** (**UInt8**), **Int16**, or **BFloat16**. Trivial register types (**Int**, **Float64**, **SIMD**) are **ImplicitlyCopyable** with no destructor. A copy is a register copy; **^** is a no-op (the compiler warns transfer has no effect); there's nothing to destroy. The rules still apply, they just compile to register moves or nothing. Values aren't mutable or immutable. Access is. | Name | Meaning | |------|----------------------------------------------| | var | always mutable | | ref | inherits the mutability of what it refers to | | Method | Meaning | |-----------------------------------------------|-----------| | \_\_init\_\_(out self, …) | construct | | \_\_init\_\_(out self, \*, copy: Self) | copy | | \_\_init\_\_(out self, \*, deinit move: Self) | move | | \_\_deinit\_\_(deinit self) | destroy | Copy, move, and destructors can't raise. The var assignment table shows which one each assignment runs. ```mojo var data: List[String] = ["a", "b", "c", "d", "e"] var s = data[1:3] # a Span view, no copy: [b, c] s[0] = "X" # writes through: data is [a, X, c, d, e] var text = "Hello, World!" var hi = text[codepoint=0:5] # a StringSpan view: "Hello" ``` A view is a non-owning window into a buffer someone else owns. **Span** views contiguous elements; **StringSpan** views UTF-8 text. Like **ref**, a view carries an origin, so the compiler keeps the source alive and tracks whether the view stays valid. | You write | What it does | |---------------------|----------------------------------------| | return x | copy out (when **ImplicitlyCopyable**) | | return x.copy() | copy out | | return x^ | transfer out | | -> T | return a value | | -> ref[origin] T | return a reference | Like a var assignment, **return x** copies; when **x** is at its last use the compiler moves it instead (you own it, so it can be moved). No **-> T^**: the **^** goes on the returned value in **return x^**, not on the return type. --- ## Mojo traits cheat sheet Sheet, Panel, } from '@site/src/components/CheatSheets/sheet'; {/* markdownlint-disable MD033 MD034 */} ## Lifecycle no requirements | Signature | Remark | |------------------------------------|-------------------| | `__deinit__(deinit self, /)` | provided | | `comptime __del__is_trivial: Bool` | compile-time flag | Automatically added to every eligible type (all fields are also **Deinitable**). When the type is trivial, Mojo skips the destructor. | Signature | Remark | |--------------------------------------------|-------------------| | `__init__(out self, *, deinit move: Self)` | provided | | `comptime __move_ctor_is_trivial: Bool` | compile-time flag | Required to store a type in **List**, **Optional**, or **Variant**, or to return it by move. **Refines:** Movable | Signature | Remark | |-----------------------------------------|-------------------| | `__init__(out self, *, copy: Self)` | provided | | `copy(self) -> Self` | provided | | `comptime __copy_ctor_is_trivial: Bool` | compile-time flag | The copy constructor is synthesized if all fields are **Copyable**. **Refines:** Copyable < Movable · no new requirements Can mask logical errors and hide code reasoning. Prefer **Movable** or **Copyable**. | Signature | Remark | |----------------------|--------| | `__init__(out self)` | | Reach for this when you need parameterized default-construction without arguments. **Refines:** Movable · no requirements (marker) No stable address: you can't take the address of **self** in imm-convention methods. **Identifiable** is meaningless for these types. Moved trivially; all fields must also conform. **Refines:** ImplicitlyCopyable < Copyable < Movable, Deinitable, RegisterPassable · no requirements (marker) A type whose values are treated as basic bit patterns. No constructors or destructors needed. All fields must also conform. ## Format | Signature | Remark | |-------------------------------------------------|----------| | `write_to(self, mut writer: Some[Writer])` | provided | | `write_repr_to(self, mut writer: Some[Writer])` | provided | If all fields conform, you inherit both methods through reflection. String, FileHandle, FileDescriptor conform | Signature | Remark | |----------------------------------------------|----------| | `write_string(mut self, string: StringSpan)` | | | `write[*Ts: Writable](mut self, *args: *Ts)` | provided | Use for loggers, network streams, string builders, etc. ## Testing **Refines:** Deinitable, Movable | Signature | Remark | |------------------------------------------------------|-----------------| | `Value: Copyable & Deinitable` | associated type | | `value(mut self, mut rng: Rng) raises -> Self.Value` | | Allows strategies to carry and advance state between draws. **value()** draws one sample from the random number generator. ## Accelerator traits | Signature | Remark | |---------------------------------------|--------------------| | `comptime device_type: AnyType` | the on-device type | | `_to_device_type(self, mut enc, ...)` | DeviceContext hook | A host type implements this so it can be handed to a GPU or other accelerator. **DeviceContext** calls the conversion hook to turn host into device at kernel launch. | Signature | Remark | |------------------------------------|-----------------------------------| | `target() -> _TargetType` | device target | | `encode_device_ptr(mut self, ...)` | required | | `encode[T](mut self, value, dst)` | provided (+ fields, tuple, array) | Encodes a value's fields into the accelerator's data layout. ## Compare & hash | Signature | Remark | |-------------------------------------|----------| | `__eq__(self, other: Self) -> Bool` | provided | | `__ne__(self, other: Self) -> Bool` | provided | Don't use with floating-point values (use **isclose()**). **NaN != NaN**. Mojo provides a fieldwise default. Override for caches, internal metadata, and custom behavior. **Refines:** Equatable | Signature | Remark | |-----------------------------------|----------| | `__lt__(self, rhs: Self) -> Bool` | | | `__gt__(self, rhs: Self) -> Bool` | provided | | `__le__(self, rhs: Self) -> Bool` | provided | | `__ge__(self, rhs: Self) -> Bool` | provided | Implement **\_\_lt\_\_()** unless it's expensive. If so, override all four. KeyElement = Hashable + Equatable + Movable | Signature | Remark | |--------------------------------------------|----------| | `__hash__(self, mut hasher: Some[Hasher])` | provided | | Signature | Remark | |-----------------------------------------------------|--------| | `__init__(out self)` | | | `_update_with_bytes(mut self, data: Span[Byte, _])` | | | `_update_with_simd(mut self, value: SIMD[_,_])` | | | `update(mut self, value: Some[Hashable])` | | | `finish(var self) -> UInt64` | | Hashers remain alive after finalization. All three update methods are required. | Signature | Remark | |--------------------------------------|----------| | `__is__(self, rhs: Self) -> Bool` | | | `__isnot__(self, rhs: Self) -> Bool` | provided | Excludes register-passable types, which don't have stable addresses. ## Convert | Signature | Remark | |-------------------------------------|------------------| | `__bool__(self) -> Bool` | Boolable | | `__int__(self) -> Int` | Intable | | `__int__(self) raises -> Int` | IntableRaising | | `__float__(self) -> Float64` | Floatable | | `__float__(self) raises -> Float64` | FloatableRaising | If the method raises, use the Raising variant. Boolable unlocks if / while / and / or usage. ## Math | Signature | Remark | |-----------------------------------------|------------------------------------| | `__abs__(self) -> Self` | **Absable** · abs() | | `__pow__(self, exp: Self) -> Self` | **Powable** · pow(), `**` | | `__round__(self) -> Self` | **Roundable** · round() | | `__round__(self, ndigits: Int) -> Self` | **Roundable** · round(), precision | | Signature | Remark | |---------------------------|-------------------------| | `__ceil__(self) -> Self` | **Ceilable** · ceil() | | `__floor__(self) -> Self` | **Floorable** · floor() | | `__trunc__(self) -> Self` | **Truncable** · trunc() | | Signature | Remark | |-------------------------------------------------------|--------------------| | `__ceildiv__(self, denominator: Self) -> Self` | CeilDivable | | `__ceildiv__(self, denominator: Self) raises -> Self` | CeilDivableRaising | **Refines:** ImplicitlyCopyable < Copyable < Movable | Signature | Remark | |------------------------------------------------------------|--------| | `__divmod__(self, denominator: Self) -> Tuple[Self, Self]` | | Math outlier. The tuple is (quotient, remainder). ## Iterate | Signature | Remark | |-------------------------------|--------------| | `__len__(self) -> Int` | Sized | | `__len__(self) raises -> Int` | SizedRaising | | Signature | Remark | |---------------------------------------------------------------------------------------------|-----------------| | `IteratorType[iterable_mut: Bool, //, iterable_origin: Origin[mut=iterable_mut]]: Iterator` | associated type | | `__iter__(ref self) -> Self.IteratorType[origin_of(self)]` | | Parameterized on mutability and origin. Yields references tied to the source lifetime. | Signature | Remark | |------------------------------------------------|-----------------| | `IteratorOwnedType: Iterator` | associated type | | `__iter__(var self) -> Self.IteratorOwnedType` | | No origin tracking. **Refines:** Deinitable, Movable | Signature | Remark | |-----------------------------------------------------------|-----------------| | `Element: Movable` | associated type | | `__next__(mut self) raises StopIteration -> Self.Element` | | | `bounds(self) -> Tuple[Int, Optional[Int]]` | provided | | `nth(var self, n: Int) -> Optional[Self.Element]` | provided | Requires an **Iterable** on the collection, and **Iterator** on the iterator. Don't rely on **bounds()** for safety checks. It's a hint. Typed raises (**StopIteration**). ## Interop Path conforms | Signature | Remark | |------------------------------|--------| | `__fspath__(self) -> String` | | **Refines:** Deinitable | Signature | Remark | |-----------------------------------------------------|--------| | `to_python_object(var self) raises -> PythonObject` | | **Refines:** Copyable < Movable, Deinitable | Signature | Remark | |--------------------------------------------------|--------| | `__init__(out self, *, py: PythonObject) raises` | | --- ## Mojo types & literals cheat sheet Sheet, Panel, } from '@site/src/components/CheatSheets/sheet'; {/* markdownlint-disable MD033 MD034 */} ```mojo # Every fixed-width number is a 1-lane SIMD # Float32 = Scalar[DType.float32] # = SIMD[DType.float32, 1] var v = SIMD[DType.float32, 4](1.0, 2.0, 3.0, 4.0) var d = v * 2.0 # [2, 4, 6, 8], all lanes v[0] = 5.0 # write one lane print(v.reduce_add()) # sum of lanes (14.0) ``` Width must be a power of two and is part of the type; its parameter type is **SIMDLength**. ```mojo SIMD[DType.float32, 4] # DType picks the lane type Scalar[DType.int] # == Int ``` Names mirror the types: **DType.float32** ↔ **Float32**, **DType.int8** ↔ **Int8**, **DType.bool** ↔ **Bool**. A **DType** is a name, not a type. It parameterizes **SIMD**, which stores the data. ```mojo var n = 42 # Int: machine width var u: UInt = 42 # machine width var small: UInt8 = 255 var big: Int64 = -9_000_000_000 ``` | Type | Meaning | |-----------------|---------------------------------| | Int / UInt | machine word (typically 64-bit) | | Int8 … Int256 | sized signed | | UInt8 … UInt256 | sized unsigned | | Byte | alias for UInt8 | Use **Int** for counts and indices; sized types when bit width is part of the contract. Each is an alias for a 1-lane SIMD. | Type | Meaning | |-----------------|---------------------------| | Float64 | IEEE double (default) | | Float32 | IEEE single | | Float16 | IEEE half | | BFloat16 | brain float (ML training) | | Float8_e4m3fn … | 8-bit (GPU, ML) | | Float4_e2m1fn | 4-bit (Blackwell+) | No bare **Float** type. Each is an alias for a 1-lane SIMD. | Name | Meaning | |-----------------------|------------------------------------------| | `bit_width_of[Int]()` | 64 on most platforms (from std.sys.info) | | UInt8.MAX | 255 | | Int8.MIN | -128 | | Float32.MAX_FINITE | largest finite | | Float32.MAX | may be inf | IEEE floats carry **inf**, **-inf**, **nan**, **-0.0**. ```mojo var i = 42 var f = Float64(i) # Int -> Float64 var s = Int8(i) # Int -> Int8 var back = Int(Int64(i)) # round trip # between SIMD-based types: .cast[] var g = f.cast[DType.int32]() ``` Variables never convert implicitly; the compiler enforces it. Literals convert only when it can prove the result is exact. | Literal | Meaning | |----------------------|---------------------------------------------------| | 42 | decimal Int | | 0xFF 0o52 0b1010 | hex, octal, binary | | 1_000_000 | underscores group digits | | 3.14 .5 2. 2.5e-3 | floats | | 2 ** 200 | comptime IntLiteral, comptime arbitrary precision | Leading zeros on base-10 integers are rejected. At runtime literals materialize to **Int** / **Float64**. ```mojo [1, 2, 3] # Array, length in the type {"id": 1, "qty": 9} # Dict (1, "a", 2.0) # Tuple, mixed types ``` Unannotated, a bracket literal defaults to **Array**. It adapts to the type you ask for: **var x: List[Int] = [1, 2, 3]**. ```mojo "double" 'single' # triple quotes: newlines and indentation included """line one line two""" r"C:\raw\path" # raw: no escape processing "\u20AC" # lowercase \u, 4 digits: € (EURO) "\U0001F44B" # uppercase \U, 8 digits: 👋 (above U+FFFF) # adjacent literals join, same line or across lines: "Hello" " world!" # -> "Hello world!" "Content of line 1. " "Content of line 2." ``` Escapes: | Escape | Meaning | |------------|------------------------| | \n \t | newline, tab | | \\" \\\\ | quote, backslash | | \xHH | byte (2 hex digits) | | \uHHHH | Unicode (4 hex digits) | | \UHHHHHHHH | Unicode (8 hex digits) | Source is UTF-8. **\u** and **\U** reject surrogate code points (U+D800 to U+DFFF); code points above U+FFFF need **\U**, not a surrogate pair. ```mojo var who = "Mojo" t"Hi, {who}!" # interpolation t"sum = {1 + 2}" # any expression t"{{literal braces}}" # -> {literal braces} rt"raw\path {who}" # raw t-string: \ literal, still interpolates String(t"x = {who}") # cast to String ``` Interpolations evaluate at runtime. | Name | Meaning | |------------|-------------------------------| | True False | boolean values | | None | the only NoneType value | | Self | the enclosing type | | _ | discard a value in assignment | | ... | marks a required trait method | - **Int width is platform-dependent** — use Int64 for a fixed width. - **Integer overflow wraps** — Int8(127) + 1 is -128. - **Float-to-int truncates toward zero** — Int(Float64(3.9)) is 3. - **Float8 needs a GPU** — no runtime CPU arithmetic. - **Int128 / Int256 are software-emulated.** - No implicit numeric conversion: `Int + Float64` is an error. Cast with `Float64(n)`, `Int(x)`. - Numbers are fixed-width SIMD scalars, not arbitrary-precision `int`; only a **comptime** **IntLiteral** is unbounded. - No bare `float` or `int` — pick **Int**, **Float64**, or a sized type. - Every scalar is a 1-lane **SIMD**; vectorizing widens the lane count, not a new type. - Integer overflow **wraps** (defined), not C++ undefined behavior or a Rust debug panic. - **DType** is a value-level tag that parameterizes **SIMD**, not a type alias. - **Int** is C++ `ssize_t` / Rust `isize`, not C++ `int`, which is usually 32-bit. --- ## Mojo closure declarations reference A *closure* is a nested function with a *capture list* that controls how it accesses values from the scope it's nested in: ```mojo def main(): var multiplier = 3 def scale(x: Int) {imm multiplier} -> Int: return x * multiplier print(scale(5)) # 15 ``` `{imm multiplier}` references `multiplier` from the enclosing scope as an immutable reference. Without the capture list, referencing any outer value is a compile error. A closure can't outlive the scope where it's declared. Mojo doesn't support escaping closures or async execution. :::note A closure is Copyable when every value it captures is Copyable. A closure can be copied manually into heap-allocated memory, but Mojo doesn't provide a built-in mechanism for heap-allocated or existential closures. ::: ## Closure syntax ```text def name(argument-list) {capture-list} -> ReturnType: body def name[parameter-list](argument-list) {capture-list} -> ReturnType: body def name(argument-list) raises {capture-list} -> ReturnType: body ``` Effects (for example, `raises`) go between the argument list and the capture list. The capture list appears immediately before the return arrow. It can be empty (`{}`) or omitted entirely; both forms prohibit references to outer values. The argument list, parameter list, effects, return type, and `where` clauses follow the same rules as top-level functions. See [Function declarations](./function-declarations.mdx). ## Capture list grammar A capture list is a brace-enclosed, comma-separated sequence of *entries*: | Form | Meaning | |----------------|-----------------------------------------------| | ` name` | Capture `name` with convention `` | | `` | Default convention for all free variables | | `name` | Capture `name` with convention `imm` | | ` name^` | Move-capture (only with `var` or no ``) | `` is one of `imm`, `mut`, `var`, or `ref`. Position within the list isn't significant: `{mut, var z}` and `{var z, mut}` are equivalent. Trailing commas are accepted. At most one entry can omit a name (the default-convention entry). A second unnamed entry produces an error, naming the default capture convention duplication. You may only use the `^` marker on `var` entries or entries with no convention keyword. ## Capture conventions | Convention | Form | Storage in closure | Lifetime tie to outer | |------------|--------------------------|-----------------------------------|-----------------------| | `imm` | `{imm name}` / `{imm}` | Immutable reference | Live | | `mut` | `{mut name}` / `{mut}` | Mutable reference | Live | | `ref` | `{ref name}` / `{ref}` | Reference, mutability from origin | Live | | `var` | `{var name}` / `{var}` | Owned copy | Independent | | Move | `{var name^}` | Owned, consumed from outer | Consumes outer | | Copyable | `{var^}` | Owned, closure is `Copyable` | Independent | ## `imm` Immutable reference. The default convention. The closure reads the outer value each time it's called, rather than capturing a copy: ```mojo def main(): var limit = 10 def check(x: Int) {imm limit} -> Bool: return x < limit print(check(5)) # True limit = 3 print(check(5)) # False ``` `{imm}` with no variable name applies `imm` to every free variable in the body. A bare name without a convention keyword also defaults to `imm`: `{x}` is equivalent to `{imm x}`. ## `mut` Mutable reference. Writing to a captured value inside the closure modifies its binding in the outer scope: ```mojo def main(): var total = 0 def accumulate(x: Int) {mut total}: total += x accumulate(10) accumulate(20) print(total) # 30 ``` `{mut}` with no variable name applies `mut` to every free variable in the body, capturing each one by mutable reference. ## `var` Owned copy. The closure receives its own copy of the value when the closure is declared. Later changes to the outer binding don't affect the closure's copy, and vice versa: ```mojo def main(): var snapshot = 42 def frozen() {var snapshot} -> Int: return snapshot snapshot = 999 print(frozen()) # 42 ``` `{var}` with no variable name copies every free variable referenced in the closure body. The copy initializer runs once per closure declaration. Capturing a large `List` or `String` by `var` allocates at that point. Use `imm` or `mut` when an independent copy isn't needed. ## Move capture: `var name^` Transfers ownership of `name` into the closure. The outer binding is consumed; using it after the closure declaration is a compile error: ```mojo def main(): var data: List[Int] = [1, 2, 3] def take_data() {var data^}: print(data) take_data() # [1, 2, 3] # print(data) # error: 'data' is uninitialized # after move ``` Move capture skips the copy that `var name` would perform and is the only way to capture a move-only type by value. Constraints: - Only legal after `var` or after a bare name with no convention. - `{imm name^}`, `{mut name^}`, and `{ref name^}` are rejected. - A bare `name^` is equivalent to `var name^`. ## Copyable closures: `var^` `{var^}` with no variable name makes move capture the default for every free variable in the body. When every captured type is `Copyable`, the resulting closure value is also `Copyable`: ```mojo def main(): var label = "sensor-1" def tag() {var^} -> String: return label var clone = tag # closure value copied print(tag()) # sensor-1 print(clone()) # sensor-1 ``` Copying the closure invokes the copy initializer of each captured value. The copy happens at the assignment, not at the closure declaration. Constraints: - `{var^}` is a default-convention entry. A capture list can contain at most one default-convention entry. - If any captured type is move-only, the closure is `Movable` but not `Copyable`. Comparison with `{var name^}`: | Form | Captured names | Closure value | |---------------|-------------------------------|---------------------------------------| | `{var name^}` | Only `name`, by move | Not `Copyable` by default | | `{var^}` | All referenced names, by move | `Copyable` if captures are `Copyable` | ## `ref` Reference whose mutability comes from the outer binding's origin. The closure doesn't choose `imm` or `mut`; it preserves the mutability of that origin: ```mojo def show_mutability(ref items: List[Int]): def report() {ref items}: comptime if origin_of(items).mut: print("mut") else: print("immut") report() # `xs` uses default `imm` convention, immutable reference def from_imm(xs: List[Int]): show_mutability(xs) # `xs` uses `mut` convention, mutable reference def from_mut(mut xs: List[Int]): show_mutability(xs) def main(): var nums: List[Int] = [10, 20, 30] from_imm(nums) # immut from_mut(nums) # mut ``` `ref` is the only convention that forwards origin information unchanged. `imm` and `mut` create references with fixed mutability; `var` removes the origin relationship entirely. `ref` captures are intended for parameterized code that must work with different mutability contexts. In ordinary closures, `imm` and `mut` produce clearer signatures. ## Empty and omitted capture lists An empty capture list (`{}`) has the same result as omitting the capture list: any reference to an outer value is rejected with an error about inferring the capture convention. Both forms allow a body that uses only its arguments and locally declared values. The function behaves as a plain nested function without captures. Prefer `{}` when the absence of captures is intentional. The explicit braces make the constraint visible at the declaration. ## Mixing conventions Each captured value can use its own convention: ```mojo def main(): var config = "prod" var count = 0 var label = "run-1" def process() {imm config, mut count, var label}: count += 1 print(config, count, label) process() # prod 1 run-1 label = "run-2" process() # prod 2 run-1 # (label was copied at declaration time) ``` A bare name in a mixed list uses `imm`, not the convention of surrounding entries: ```mojo # y is captured as 'imm', not 'mut' def f() {var z, mut x, y}: # ... ``` ## Default convention A convention keyword without a name sets the default for every free variable not explicitly named: ```mojo def main(): var a = 1 var b = 2 var z = "snapshot" def mixed() {mut, var z}: a += 10 # 'a' uses default: mut b += 20 # 'b' uses default: mut print(a, b, z) mixed() # 11 22 snapshot z = "changed" mixed() # 21 42 snapshot # ('z' was copied at declaration) ``` Rules: - A capture list can contain at most one default-convention entry. - Position within the list isn't significant. - Trailing commas are accepted. - The default doesn't apply to names covered by an explicit entry. In `{mut, var z}`, `var z` overrides the default for `z`. ## Parametric closures A closure can declare its own compile-time parameter list: ```mojo def main(): # The `Intable` trait supports `Int` conversion def double[T: Intable](x: T) {} -> Int: return Int(x) * 2 print(double[Int](5)) # 10 print(double[Float64](3.4)) # 6 ``` The parameter list, capture list, effects, and return type appear in the same order as on top-level functions: `name[parameters](arguments) effects {captures} -> ReturnType`. Parameters and captures work independently. Parameters are supplied at each call site, while the capture list controls the closure's relationship to the enclosing scope. Variadic parameters are also supported (`def closure[*Ts: Coord](*args: *Ts)`). ## Effects Effects appear between the argument list and the capture list. | Effect | Form | Type example | |-----------------|---------------------------------|----------------------------------------| | `raises` | `(args) raises {captures} -> T` | def (String) raises -> Int | | `thin` | `(args) thin -> T` | def (T) thin -> U | | `abi(language)` | `(args) abi(language) -> T` | def (Float64) thin abi("C") -> Float64 | :::caution You may not combine non-Mojo `abi()` effects with raising functions. Raising functions change calling conventions in a non-obvious ways. The Mojo compiler: - accepts `def (String) abi("Mojo") raises` - rejects `def (String) abi("C") raises` ::: `raises` example: ```mojo def main() raises: var y = 2 def divide(x: Int) raises {var y} -> Int: if y == 0: raise Error("divide by zero") return x // y print(divide(10)) # 5 ``` The `thin` effect applies to function *types*, not to closure declarations. `thin` describes a non-capturing function type, so it can't represent a closure that captures. A `thin` function type is also the only one that accepts trailing `where` clauses. See [Function declarations](./function-declarations.mdx). ## Nesting Closures can nest inside closures. Each level has its own capture list. An inner closure can capture a name already captured by its enclosing closure: ```mojo def main(): var y = 4 def outer() {var y} -> Int: def inner() {var y} -> Int: return y return inner() + y print(outer()) # 8 ``` An inner closure can capture an outer closure by name. This is how nested callbacks compose: ```mojo def main(): def make_adder(n: Int): def add(x: Int) {var n} -> Int: return x + n def twice(x: Int) {var add} -> Int: return add(add(x)) print(twice(5)) # ((5 + 3) + 3) = 11 # add(add(5)) = add(8) = 11 make_adder(3) ``` Closures are values and can be used in capture lists. An inner closure captures an outer closure by `var`, `imm`, `mut`, or `ref`, just like any other value. ## Capture-list errors | Compiler complaint | Trigger | |-----------------------------------------------------------------------------------|------------------------------------------------------------------------------| | Transfer sigil `^` without `var` convention | `^` after `mut`, `imm`, or `ref` | | Duplicate default convention | Two bare convention keywords in one list | | Unrecognized token in capture position | Token that isn't a convention keyword or name | | Missing comma between entries | Identifier followed by an unrecognized token | | Unterminated capture list | Missing closing `}` | | Outer name not covered by capture list | Body references an outer name the capture list doesn't cover | | Use after move capture | Reference to a name after `{var name^}` consumed it | ## Restrictions - **No escape.** A closure can't outlive its enclosing scope. Returning a closure from its declaring function or storing it past the enclosing scope's end isn't supported. - **No `thin` on declarations.** These apply to function types, not closure declarations. A declaration with captures can't be `thin`. - **Trait conformance with closure fields.** A struct can contain a closure-typed field and conform to a trait, but every method of that trait must be declared `capturing` until the capturing effect is removed (see `unified_closure_structs.mojo`). --- ## Mojo compound statements reference A *compound statement* has a header and a body. The header ends with `:` and is followed by an indented block with the body. The body can contain simple statements, other compound statements, or both. ```mojo if condition: # Header do_something() # Body ``` The body must be indented more than the header. The first body statement sets the indentation for the rest of the body: ```mojo if condition: do_something() do_more() # Error because statement has excess indentation ``` ## If statements An `if` statement executes a block conditionally: ```mojo if x > 0: print("positive") elif x < 0: print("negative") elif x == 0: print("zero") else: print("you should never get here") ``` Conditions are evaluated in order. Add as many as needed. The first true condition runs its block, and the statement exits. The `else` block runs if no condition is true. When the body is a single simple statement, you can write it on a single line, although many style guides discourage this. ```mojo if x > 0: print("positive") ``` Common shortcuts from other languages won't work in Mojo: ```mojo x > 0 and print("positive") # Error because 'None' isn't truthy print("positive") if x > 0 else pass # Error because 'pass' isn't an expression ``` ## While loops The `while` loop repeats its body while a condition is true: ```mojo var count = 0 while count < 10: print(count) count += 1 ``` Use `break` to exit the loop early and `continue` to skip to the next iteration: ```mojo while True: var item = get_next() if item is None: break # Exit loop if no more items if not is_valid(item): continue # Skip invalid items process(item) # Only runs for valid items ``` ## For loops The `for` statement iterates over a sequence: ```mojo for item in items: process(item) for i in range(10): # [0, 10) print(i) ``` To support iteration, a sequence must implement `__iter__()` and `__next__()`. A `for` loop desugars to a `while` loop that uses these methods. Destructuring works directly in the loop target. This lets you unpack tuple elements as you iterate. In this example, each item in `pairs` is unpacked into `key` and `value` for every iteration: ```mojo for key, value in pairs: # For example, [("a", 1), ("b", 2), ...] print(key, value) ``` ### Loop variable bindings Use `var` and `ref` conventions to control ownership, copying, and mutability behavior in loop variables. By default, loop variables are immutable references to the iterated items (`imm`). To create a mutable copy, use `var`. To maintain value mutability, use `ref`: ```mojo var list: List[String] = ["a", "b", "c", "d"] for var item in list: item = item + "x" # works. item is mutable copy of list element print(item) # prints "ax", then "bx", "cx", and "dx" print(list) # unchanged for ref item in list: item = item + "x" # mutability picked up in reference to list element print(item) # prints "ax", then "bx", "cx", and "dx" print(list) # changed to ["ax", "bx", "cx", "dx"] ``` ## Loops and else clauses An optional `else` clause runs when the loop exits normally. It does not run if the loop exits with `break`: ```mojo var found = False for item in items: if item == target: found = True break else: print("not found") # Only runs if break was never hit ``` Both `for` and `while` loops support `else`. ## Error handling A `try` statement executes code that may raise errors. ```mojo var result: Bool try: result = risky() except e: handle(e) ``` ### Structure Each `try` statement requires at least one `except` or `finally` clause: ```mojo try: operation() except e: handle_error(e) # Runs if an error occurs else: on_success() # Runs only if no error occurred finally: cleanup() # Always runs ``` Execution proceeds in a fixed order: 1. The `try` block runs first. 1. If an error occurs, the matching `except` block runs. 1. If no error occurs, the `else` block (if present) runs after the `try` block. 1. If included, a `finally` block always runs last. ### Error binding Bind the error to a name with `except name`: ```mojo try: risky() except e: print(e) # e is the caught error ``` Without a binding, the error is caught but not accessible. There is no default error variable. This is useful when you want to respond to an error state without needing the error details: ```mojo try: risky() except: print("something went wrong") ``` ### Typed errors When a function declares a specific error type with `raises ErrorType`, the bound variable's type is inferred: ```mojo @fieldwise_init struct NetworkError: var message: String var code: Int def fetch() raises NetworkError -> String: raise NetworkError("HTCPCP", 418) # See RFC 2324 * try: var result = fetch() except e: # e is inferred as `NetworkError` print(e.message) # Known types support direct field access print(e.code) # `.code` and `.message` only work because `e` # is a known to be `NetworkError` ``` A `try` block handles one error type. The compiler raises an error if code in the `try` block can raise more than one error type. :::note [RFC 2324](https://datatracker.ietf.org/doc/html/rfc2324) defines the Hyper Text Coffee Pot Control Protocol (HTCPCP) as an April Fools' joke. ::: ## Context managers A `with` statement manages resources using context managers. *Context managers* define setup (`__enter__`) and cleanup (`__exit__`) operations. The cleanup always runs when the block exits, even if an error occurs: ```mojo with open("file.txt") as f: var content = f.read() # File is closed here, even if an error occurred ``` Multiple context managers can share a single `with` statement: ```mojo with open("input.txt") as f_in, open("output.txt", "w") as f_out: f_out.write(f_in.read()) ``` This is equivalent to nested `with` statements. ### How context managers work When a `with` block is entered, `__enter__()` is called on the context manager expression. The result is bound to the `as` target if present. A context manager that defines only `__enter__()` is valid; `__exit__()` is optional. When the block exits, `__exit__()` is called if it exists, even if an error occurs. A minimal custom context manager: ```mojo struct Scope(ImplicitlyCopyable): var label: String def __init__(out self, label: String): self.label = label def __enter__(self) -> Self: # perform setup tasks print("entering", self.label) return self def __exit__(self): # perform cleanup tasks print("exiting", self.label) def main(): with Scope("setup") as s: print("inside", s.label) # entering setup # inside setup # exiting setup ``` `__enter__` returns `self` so the `as` target binds to the manager. The `ImplicitlyCopyable` conformance lets the compiler return `self` by value. ## Compile-time control flow `comptime if` and `comptime for` run at compile time. The condition or sequence must be a compile-time value or expression. Use them to generate code based on compile-time conditions. You cannot use runtime values in `comptime` statements. ### comptime if `comptime if` selects a branch at compile time, pruning the unselected branches. Only the selected branch appears in the compiled program. ```mojo from std.sys import size_of comptime if size_of[Int]() == 8: print("64-bit") else: print("Probably 32-bit") ``` The condition must be a compile-time expression. In this example, `runtime_value` is not available at compile time, so the code errors during compilation: ```mojo comptime if runtime_value > 0: # Error because 'comptime if' requires pass # compile-time evaluation ``` `comptime if` supports `elif` and `else` like the regular `if` statement. ### comptime for `comptime for` unrolls a loop at compile time. Each iteration is compiled as separate code. This creates a bigger binary but improves runtime performance by eliminating loop overhead and enabling further optimizations. ```mojo comptime for i in range(3): print(i) # Compiled as: print(0); print(1); print(2) ``` Use `comptime for` to generate repeated code patterns or iterate over compile-time sequences. ## Scopes Each compound statement body creates a new scope. Variables declared inside a body are not visible outside it: ```mojo if condition: var x = 10 print(x) # Error: x is not in scope ``` `with` statement variables bound with `as` are scoped to the `with` block: ```mojo with open("file.txt") as f: var data = f.read() # f is not accessible here ``` Nested functions create their own scope and can capture variables from enclosing functions with capture lists: ```mojo def outer(): var count = 0 def inner() {mut count}: # Capture count by mutable reference count += 1 # Updates captured count inner() print(count) # 1 ``` --- ## @align The `@align` decorator specifies a minimum memory alignment for values of a struct type. If you already work with low-level memory, SIMD, or GPUs, you can think of `@align` as a way to make alignment a property of the type itself, enforced by the compiler. If not, the short version is this: alignment controls where values are placed in memory, and some hardware requires or benefits from specific alignments. Most Mojo code doesn't need explicit alignment. You only need `@align` when your types need placement at specific boundaries, such as when interacting with GPU buffers, using SIMD instructions, or avoiding cache-line contention in concurrent code. Without `@align`, alignment requirements must be tracked manually and enforced at allocation sites. With @align, the requirement becomes part of the type, and the compiler ensures it's respected everywhere the type is used. ## What alignment means Every byte in memory has an address. An address is N-byte aligned if its address is evenly divisible by N. Examples: - 8-byte aligned addresses: 0, 8, 16, 24, etc. - 64-byte aligned addresses: 0, 64, 128, 192, etc. Hardware often loads memory in fixed-size chunks, such as cache lines. When a value begins at an aligned address, it fits cleanly within those chunks. When it doesn't, hardware may need extra memory accesses, or may reject the access entirely. Alignment affects where a value begins in memory, and it also affects how large the value is: Mojo rounds a struct's size up to a multiple of its alignment. ## Basic usage Add `@align(N)` to your struct definition, where `N` is a positive power of 2 and the number represents the _minimum_ required alignment in bytes: ```mojo from std.sys import align_of @align(64) struct CacheAligned: var data: Int def main(): print(align_of[CacheAligned]()) # Prints 64 ``` In this example, CacheAligned is aligned to 64 bytes, even though Int normally requires only 8-byte alignment. ## Determining alignment The actual alignment of a struct is the maximum of: - The value specified by @align(N), if present. - The struct's natural alignment (the maximum alignment of its fields). - The alignment requirements of any embedded aligned fields. You can't reduce alignment below the natural alignment of the struct. The `@align` decorator specifies a _minimum_, not an override: ```mojo from std.sys import align_of @align(4) struct TryToReduce: var x: Int # Int has 8-byte natural alignment def main(): print(align_of[TryToReduce]()) # Prints 8 ``` When a struct contains an aligned field, the outer struct inherits that alignment: ```mojo from std.sys import align_of @align(64) struct CacheAligned: var x: Int struct Container: var aligned: CacheAligned var other: Int def main(): print(align_of[Container]()) # Prints 64 ``` ## Stack and heap behavior Both stack and heap allocations respect `@align`: ```mojo from std.sys import align_of from std.memory import alloc, dealloc @fieldwise_init @align(64) struct CacheAligned: var data: Int def use_aligned(): # Stack allocation var stack_value = CacheAligned(42) # Heap allocation var heap_alloc = alloc[CacheAligned]({count = 1}) dealloc(heap_alloc^) ``` You don't need to manually request alignment when allocating values of an aligned type. ## Alignment and arrays The `@align` decorator guarantees alignment of the base address of a value, including the base pointer of an array. It pads the size of the struct to be a multiple of the alignment. Mojo lays out array elements using `size_of[T]()` which rounds up to a multiple of `align_of[T]()`: ```mojo from std.sys import align_of, size_of from std.memory import alloc, dealloc @align(64) struct CacheAligned: var data: Int # 8 bytes def demonstrate_array_stride(): var allocation = alloc[CacheAligned]({count = 4}) var arr = allocation.unsafe_ptr() print(align_of[CacheAligned]()) # 64 print(size_of[CacheAligned]()) # 64 # All elements of arr are guaranteed to be 64-byte aligned dealloc(allocation^) ``` ## Parameterized structs Alignment also works with parameterized structs. All instances of a parameterized type share the same alignment requirement. Because of this, under certain circumstances, you may find that the alignment isn't tuned by its types: ```mojo from std.sys import align_of @fieldwise_init @align(128) struct AlignedType[T: Copyable & Deinitable]: var value: Self.T def main(): print(align_of[AlignedType[Int8]]()) # 128 print(align_of[AlignedType[Int64]]()) # 128 ``` Compare this with an alignment of 4, where the maximum of the decorator value (`N`) and the type's natural alignment produces a different result: ```mojo from std.sys import align_of @fieldwise_init @align(4) struct AlignedType[T: Copyable & Deinitable]: var value: Self.T def main(): print(align_of[AlignedType[Int8]]()) # 4 print(align_of[AlignedType[Int64]]()) # 8 ``` ## Interaction with RegisterPassable When `@align` is present, single-field register-passable structs aren't flattened. This preserves the alignment requirement: ```mojo from std.sys import align_of, size_of @align(32) struct AlignedTrivial(RegisterPassable): var value: Int def main(): print(align_of[AlignedTrivial]()) # 32 print(size_of[AlignedTrivial]()) # 32 ``` ## Requirements and errors Mojo's `@align` decorator has the following requirements: - The alignment value must be a positive power of 2. - The maximum supported alignment is 2^29 bytes. - The value must be known at compile time. - The decorator requires exactly one argument. Invalid uses produce compile-time errors: ```mojo @align(0) struct Bad1: var x: Int @align(3) struct Bad2: var x: Int @align(1073741824) struct Bad3: var x: Int @align struct Bad4: var x: Int @align(64, 128) struct Bad5: var x: Int @align("64") struct Bad6: var x: Int ``` ## Special case: @align(1) Using @align(1) is valid and produces no warning. It doesn't reduce alignment below the natural alignment of the struct. This can be useful as a fallback value in parametric code: ```mojo from std.sys import align_of @align(1) struct MinimalAlign: var x: Int def main(): print(align_of[MinimalAlign]()) # Prints 8 ``` ## Real-world example: hardware descriptors Some hardware accelerators require aligned descriptors for correctness. For example, NVIDIA's Tensor Memory Accelerator requires 64-byte aligned descriptors. Before `@align`, this required explicit allocation tricks: ```mojo # Verbose and error-prone var tensormap = my_custom_stack_allocation[1, TensorMap, alignment=64]()[0] ``` With `@align`, the type encodes your alignment requirement: ```mojo @align(64) struct TensorMap: # Descriptor fields pass var tensormap = TensorMap() var heap_tensormap = alloc[TensorMap]({count = 1}) ``` Alignment is enforced automatically everywhere the type is used. Both stack and heap allocations respect `@align`. ## Parametric alignment The alignment value can also be a struct parameter, enabling parameterized aligned types: ```mojo from std.sys import align_of @align(Self.alignment) struct AlignedBuffer[alignment: Int]: var data: Int def main(): print(align_of[AlignedBuffer[64]]()) # Prints 64 print(align_of[AlignedBuffer[128]]()) # Prints 128 ``` The alignment is validated when the struct is instantiated, so invalid values like `AlignedBuffer[3]` will produce a compile-time error. --- ## @always_inline You can add the `@always_inline` decorator on any function to make the Mojo compiler "inline" the body of the function (copy it) directly into the body of the calling function. This eliminates potential performance costs associated with function calls jumping to a new point in code. Normally, the compiler will do this automatically where it can improve performance, but this decorator forces it to do so. The downside is that it can increase the binary size by duplicating the function at every call site. For example: ```mojo @always_inline def add(a: Int, b: Int) -> Int: return a + b print(add(1, 2)) ``` Because `add()` is decorated with `@always_inline`, Mojo compiles this program without adding the `add()` function to the call stack, and it instead performs the addition directly at the `print()` call site, as if it were written like this: ```mojo print(1 + 2) ``` ## `@always_inline("nodebug")` You can also use the decorator with the `"nodebug"` argument, which has the same effect to inline the function, but without debug information. This means that you can't step into the function when debugging. This decorator is intended to be used on the low-level functions in a library, which may wrap primitive functions, MLIR operations, or inline assembly. Marking these functions as "nodebug" prevents users from accidentally stepping into low-level non-Mojo code when debugging. ## `@always_inline("builtin")` The `"builtin"` argument is like `"nodebug"`, but even stricter. The `"builtin"` version of the decorator should only be used on functions that wrap a single MLIR operation that the compiler has special compile-time handling for. It allows the compiler to inline the function when it's used in a parameter context. :::caution Using this version of the decorator requires some knowledge of the Mojo compiler's internals. Using it outside of the standard library is not recommended. Use the standard `@always_inline` decorator or the `"nodebug"` version, instead. ::: This version of the decorator does everything that `"nodebug"` does, plus two other behaviors: - It checks the body of the function to validate that it doesn't use anything that `@always_inline("builtin")` can't handle. This checks that there is no control flow, no function calls to functions that are not themselves `@always_inline("builtin")`, no use of unsupported MLIR operations, etc. - When the function is used in a parameter context, it is unconditionally inlined. For more details and background, see [the `@always_inline("builtin") proposal](https://github.com/modular/modular/blob/mojo/v1.1.0/Mojo/proposals/always_inline_builtin.md). --- ## @__copy_capture :::caution Deprecated The `@__copy_capture` decorator is deprecated and will be removed in a future release. Use the current [closure](/docs/manual/functions/closures/) syntax with capture lists. ::: You can add the `@__copy_capture` decorator on a legacy closure to capture register-passable values by copy. This decorator causes a nested function to copy the value of the indicated variable into the closure object at the point of formation instead of capturing that variable by reference. This allows you to pass the closure as a parameter, but lifetimes aren't guaranteed to be respected. ```mojo def foo(x: Int): var z = x @__copy_capture(z) @__parameter def formatter() -> Int: return z z = 2 print(formatter()) def main(): foo(5) ``` --- ## @deprecated The `@deprecated` [decorator](/docs/reference/decorators/) marks a declaration as obsolete and scheduled for removal. It actively signals to callers that an API still works today but won't stick around forever. Deprecation lets you safely reshape your codebase. With it, you can refine designs, replace older patterns, and introduce better tooling without forcing sudden changes. When you mark something as deprecated, you give your users the time and information they need to move to newer APIs or refactor their code for the upcoming feature loss before the old API disappears. ## Deprecation information Deprecation doesn't prevent using symbols. Instead, it surfaces guidance in the form of compiler warnings. Mojo offers the `@deprecated` decorator with two styles: - **`@deprecated(use=symbol)`**: _Use this style when there's a clear successor to the previous symbol._ The compiler warns callers when they use the deprecated item and points them toward the symbol you recommend. This is a gentle nudge that says, "This call still works, but you should really start using the other thing instead." - The argument for `use` is the actual symbol. Don't quote it. - The symbol must be valid or the compiler will error. - **`@deprecated("message")`**: _Use this version when you want to explain the change in your own words._ If there's no direct replacement in play, the message style lets you explain the impact of your deprecation. The compiler displays the supplied string in its warning where the deprecated item is used. A message makes it easy to steer callers, give context, or note that the feature is going away entirely. :::note Deprecation practices When deprecating an API, consider: - **Clarity**: Explain the reason for deprecation when it provides actionable context for the user. - **Actionability**: When possible, point to a concrete replacement (`use`) or next step (message). - **Consistency**: Use the same phrasing across related APIs - **Precision**: When possible, deprecate individual functions or methods rather than entire types. Deprecation is most effective when it fits into clear, predictable upgrade paths. ::: ## How to deprecate The following sample demonstrates how to apply deprecation using both built-in styles: ```mojo # Mark function `a` as deprecated with a custom message @deprecated("Sunsetting a") def a(): pass # Mark function `b` as deprecated with alternative @deprecated(use=c) def b(): pass # `c` is `b`'s recommended replacement after deprecation def c(): pass def main(): a() # custom warning b() # warning with recommended replacement c() # no warning # Demonstrate that only warnings are issued print("This is a functioning app") ``` Output: ```text deprecation.mojo:16:6: warning: Sunsetting a a() # custom warning ~^~ deprecation.mojo:3:4: note: 'a' declared here def a(): ^ deprecation.mojo:17:6: warning: 'b' is deprecated, use 'c' instead b() # warning with recommended replacement ~^~ deprecation.mojo:8:4: note: 'b' declared here def b(): ^ This is a functioning app ``` ### Items you can deprecate In Mojo, you can deprecate any of the following items: - **Structs**: ```mojo @deprecated(use=PerformantStruct) struct LegacyStruct: # ... ``` - **Functions**: ```mojo @deprecated("This function is being phased out") def legacy_function(self): pass ``` - **Traits**: ```mojo @deprecated(use=Honkable) trait Quackable: def quack(self): ... ``` - **`comptime` values**: ```mojo @deprecated("Use tau instead") comptime pi = 3.141592 ``` --- ## @doc_hidden The `@doc_hidden` [decorator](/docs/reference/decorators/) marks a declaration as hidden from documentation generation. It allows you to exclude internal implementation details, special methods, or other code from appearing in published API documentation while keeping them accessible in your source code. This decorator is particularly useful when you need to maintain internal APIs, helper methods, or alternative initializers that exist for implementation purposes but shouldn't be part of your library's public documentation. API members with names starting and ending with double underscores ("dunder" members) are always treated as public and included in documentation unless they are decorated with `@doc_hidden`. Mojo treats any other API names starting with a single or double underscore (`_` or `__`) as internal and omits them from the generated documentation—no need for `@doc_hidden`. The `@doc_hidden` decorator only affects documentation generation. Hidden declarations are fully accessible in source code and can be accessed like any other declaration. The same is true of internal declarations that start with underscores—they are internal _by convention_, there are no access restrictions. ## When to use `@doc_hidden` Use `@doc_hidden` to hide: - **Alternative initializers and dunder methods**: Hide alternative `__init__()` methods or other dunder methods that users don't need to call directly. The `@doc_hidden` decorator is especially useful for dunder methods, since they're public by default and you can't hide them by renaming them. - **Internal methods**: Hide private or internal helper methods that are implementation details. - **Deprecated internals**: Hide old internal APIs that remain for backward compatibility but shouldn't appear in new documentation. See also [`@deprecated`](/docs/reference/decorators/deprecated/). ## Usage Apply `@doc_hidden` just above any declaration you want to exclude from generated documentation: ```mojo struct Calculator: """A simple calculator struct demonstrating @doc_hidden.""" var value: Int def __init__(out self, initial_value: Int = 0): """Creates a new Calculator with an initial value. Args: initial_value: The starting value for the calculator. Defaults to 0. """ self.value = initial_value @doc_hidden def __init__(out self): """Internal initializer that should not appear in public documentation. This initializer exists for implementation purposes but users should prefer the initializer that takes an initial value. """ self.value = 0 def add(mut self, amount: Int): """Adds a value to the calculator. Args: amount: The value to add. """ self.value += amount ``` The no-argument `__init__()` initializer in this example uses `@doc_hidden`. It works in code but won't show up in the generated documentation. ## What can be hidden You can apply `@doc_hidden` to most APIs: - **Functions and methods** Hide any function or method overload, including initializers (`__init__()`) and other special methods: ```mojo struct Point: @doc_hidden def __init__(out self): pass ``` The `@doc_hidden` decorator only hides the overload immediately following the decorator. So the same function can have both documented overloads and hidden overloads. - **Entire structs** Hide helper or internal structs. ```mojo @doc_hidden struct InternalHelper: pass ``` - `comptime` values and members ```mojo @doc_hidden comptime INTERNAL_CONSTANT = 42 ``` - **Struct fields** ```mojo struct PublicStruct: @doc_hidden var implementation_detail: Int pass ``` --- ## @export You can add the `@export` decorator on any function to make it publicly available as an exported symbol in the compiled artifact, allowing it to be called from external code. An `@export` function must declare its calling convention with an explicit [`abi`](/docs/reference/function-declarations#abi-c) effect. ```mojo # This function is internal - not an exported symbol def internal_helper(): print("Internal") # This function is exported under its own name, "my_exported_function" @export def my_exported_function() abi("Mojo"): print("Exported!") internal_helper() # This function is exported under the name "my_renamed_function" @export("my_renamed_function") def my_other_function() abi("Mojo"): print("Another function.") ``` The `@export` decorator can take an optional argument: - An alternate name to export the function under, as shown above. Use the name specifier and `abi("C")` effect to export a function that complies with the C calling conventions. You must also supply a function name that is a valid C identifier. For example: ```mojo @export("my_func") def my_function( name: StaticString, ptr: OpaquePointer[MutUntrackedOrigin], ) abi("C") -> None: pass ``` :::note Initialize the runtime in shared libraries If you compile an exported function into a shared library (`mojo build --emit shared-lib`) and call it from a non-Mojo host program such as C or C++, no Mojo `main()` function runs, so the Mojo runtime is never initialized. Call [`initialize_runtime()`](/docs/std/runtime/initialize_runtime/) before calling any other standard library functions. See [Call a Mojo shared library from C or C++](/docs/tools/compilation/#call-a-mojo-shared-library-from-c-or-c) for details. ::: To call Mojo from Python, register functions with a module builder. See [Calling Mojo from Python](/docs/manual/python/mojo-from-python/) for details. --- ## @fieldwise_init You can add the `@fieldwise_init` decorator on a struct to generate the field-wise `__init__()` initializer. For example, consider a simple struct like this: ```mojo @fieldwise_init struct MyPet: var name: String var age: Int ``` Mojo sees the `@fieldwise_init` decorator and synthesizes a field-wise initializer, the result being as if you had actually written this: ```mojo struct MyPet: var name: String var age: Int def __init__(out self, var name: String, age: Int): self.name = name^ self.age = age ``` You can synthesize the copy initializer and move initializer by adding the `Copyable` trait to your struct. For more information about these lifecycle methods, read [Life of a value](/docs/manual/lifecycle/life/). ## Implicit conversion Implicit conversion lets you pass a value and lets a type build itself, without calling the initializer directly. This keeps caller code simple and clean. You enable this by marking an initializer as [`@implicit`](/docs/reference/decorators/implicit/) or using `@fieldwise_init("implicit")` to create one for you. For example, if `MyStruct` has an initializer that accepts an `Int`, you can construct an instance like this: ```mojo var an_instance = MyStruct(42) ``` A function that takes a `MyStruct` will accept that instance, an explicit initializer call, or the value that can be converted into one: ```mojo some_function(an_instance) # pass an instance some_function(MyStruct(42)) # build one directly some_function(42) # implicit conversion ``` All three forms create a `MyStruct` for the call. Some may have small compile-time or run-time differences. ### Declaring implicit initialization You can declare implicit initializers in two ways: - Use `@fieldwise_init("implicit")` to auto-create one, as long as your type has exactly _one_ instance field. This limit applies to the type itself, not just to initializer arguments. - Add [`@implicit`](/docs/reference/decorators/implicit/) to an initializer you write. The initializer can accept only one argument. :::note Read more about [initializers and implicit conversion](/docs/manual/lifecycle/life/#constructors-and-implicit-conversion). ::: ### Fieldwise and implicit example Here is a type that stores an `Int`. It can be created with an integer or from any value that can be floored and converted to an integer. It uses `@fieldwise_init("implicit")` for integers and creates an `@implicit` initializer for other values: ```mojo from std.math import Floorable, floor # Creates an implicit initializer and limits the type to one instance field. @fieldwise_init("implicit") struct FlooringInt: var floored: Int # Allows implicit conversion from types that can be floored and made into an Int. @implicit def __init__[T: Floorable & Intable](out self, value: T): self.floored = Int(floor(value)) def floored(value: FlooringInt) -> Int: return value.floored def main(): print(floored(FlooringInt(42))) # pass an instance, output: 42 print(floored(2)) # pass Int, output: 2 print(floored(52.6)) # pass Float64, output: 52 var x = BFloat16(192.3) print(floored(x)) # pass BFloat16, output: 192 var y: FlooringInt = 180 print(y.floored) # output 180 var z: FlooringInt = 3.14159 print(z.floored) # output: 3 ``` What you don't see in this example is an initializer for integers. Adding `@fieldwise_init("implicit")` lets the compiler build it for you. If you wrote this by hand, it might look like this: ```mojo @implicit def __init__(out self, floored: Int): self.floored = floored ``` --- ## @implicit You can add the `@implicit` decorator on any single-argument initializer to identify it as eligible for implicit conversion. For example: ```mojo struct MyInt: var value: Int @implicit def __init__(out self, value: Int): self.value = value def __init__(out self, value: Float64): self.value = Int(value) ``` This implicit conversion initializer allows you to pass an `Int` to a function that takes a `MyInt` argument, or assign an `Int` to a variable of type `MyInt`. However, the initializer that takes a `Float64` value is **not** an implicit conversion initializer, so it must be invoked explicitly: ```mojo def func(n: MyInt): print("MyInt value: ", n.value) def main(): func(Int(42)) # Implicit conversion from Int: OK func(MyInt(Float64(4.2))) # Explicit conversion from Float64: OK func(Float64(4.2)) # Error: can't convert Float64 to MyInt ``` ## Deprecation Over time, you may decide that an implicit conversion is no longer appropriate for your code base. For example, it may hide complexity or cause ambiguous function overloads. In such cases, Mojo lets you mark a conversion as deprecated using its built-in `deprecated` argument on the @implicit decorator. Deprecation allows you to phase out the conversion gradually instead of causing abrupt behavior changes. Supply a Boolean value to the `deprecated` argument: ```mojo struct MyStruct: @implicit(deprecated=True) def __init__(out self, value: Int): # ... ``` This tells the compiler to emit a warning when the conversion is used implicitly, without breaking existing code: ```mojo _: MyStruct = 1 # Warns on implicit conversion _ = MyStruct(1) # No warning. Conversion is explicit ``` --- ## Mojo decorators A Mojo decorator modifies or extends the behavior of a struct, function, or other declaration at compile time. You place the decorator on the line above the declaration it applies to, prefixed with `@`. ```mojo @fieldwise_init struct Point: var x: Float64 var y: Float64 ``` After the `@`, a decorator is followed by a name, with optional arguments in parentheses. Each decorator goes on its own line. You can stack multiple decorators on a single declaration: ```mojo @fieldwise_init @align(64) struct CacheLine: var data: SIMD[DType.float32, 16] ``` Decorators apply bottom-up: the one closest to the declaration is applied first. :::note No custom decorators Mojo doesn't support custom decorators. The decorators in this section are built into the compiler. ::: ## Decorators The following pages describe each built-in decorator with examples. ## Decorator targets Not every decorator works on every declaration. This table shows which decorators are valid on which targets. | Decorator | `struct` | `def` | method | `trait` | `comptime` | `var` | field | |---------------------------|----------|-----------------|--------|---------|------------|-------|-------| | `@align` | yes | | | | | | | | `@always_inline` | | yes | yes | | | | | | `@extensibility.register` | yes | | | | | | | | `@__copy_capture` | | yes1 | | | | | | | `@deprecated` | yes | yes | yes | yes | yes | | | | `@doc_hidden` | yes | yes | yes | | yes | | yes | | `@export` | | yes | | | | | | | `@fieldwise_init` | yes | | | | | | | | `@implicit` | | | yes | | | | | | `@no_inline` | | yes | yes | | | | | | `@__parameter` | | yes1 | | | | | | | `@staticmethod` | | | yes | | | | | 1`@__copy_capture` and `@__parameter` work only on *nested* functions. --- ## @no_inline You can add the `@no_inline` decorator on any function to prevent it from being inlined by the compiler. ```mojo @no_inline def my_large_function(): ... ``` Inlining is an optimization that reduces function call overhead for small, frequently-called functions. Functions can be explicitly marked for inlining using [`@always_inline`](/docs/reference/decorators/always-inline/), or may be inlined automatically by the compiler. Too many inlined functions can slow compilation and substantially increase the binary size of the compiled program. In particular, large or complex functions may not benefit as much from inlining. --- ## @__parameter :::caution Deprecated The `@__parameter` decorator is deprecated and will be removed in a future release. Use the current [closure](/docs/manual/functions/closures/) syntax instead. The previous spelling `@parameter` is still accepted with a deprecation warning. ::: You can add `@__parameter` on a nested function to create a legacy capturing closure. This means you can create a closure function that captures values from the outer scope (regardless of whether they are variables or parameters), and then use that closure as a parameter. For example: ```mojo def use_closure[func: def(Int) capturing[_] -> Int](num: Int) -> Int: return func(num) def create_closure(): var x = 1 @__parameter def add(i: Int) -> Int: return x + i var y = use_closure[add](2) print(y) def main(): create_closure() ``` ```output 3 ``` Note the `[_]` in the function type: ```mojo def use_closure[func: def(Int) capturing[_] -> Int](num: Int) -> Int: ``` This origin specifier represents the set of origins for the values that the legacy closure captures. This allows the compiler to correctly extend the lifetimes of those values. For more information on lifetimes and origins, see [Lifetimes, origins and references](/docs/manual/values/lifetimes/). --- ## @staticmethod You can add the `@staticmethod` decorator on a struct method to declare a static method. For example: ```mojo from std.pathlib import Path struct MyStruct(Movable): var data: List[UInt8] def __init__(out self): self.data = List[UInt8]() @staticmethod def load_from_file(file_path: Path) raises -> Self: var new_struct = MyStruct() new_struct.data = file_path.read_bytes() return new_struct ^ ``` Unlike an instance method, a static method doesn't take an implicit `self` argument. It's not attached to a specific instance of a struct, so it can't access instance data. For more information see the documentation on [static methods](/docs/manual/structs/#static-methods). --- ## Mojo docstring reference Mojo uses docstring literals to generate API reference documentation data, which can be processed to produce page content or read directly from source. Place docstrings immediately after declarations. Docstrings support Markdown, freeform text, labeled sections, and instructive code examples: ```mojo def greet(name: String) -> String: """Returns a greeting string for the given name. Produces a simple `"Hello, name!"` string suitable for display or logging. Args: name: The name to include in the greeting. Returns: A greeting of the form `"Hello, name!"`. """ return "Hello, " + name + "!" ``` Mojo docstrings follow conventions used by [Python docstrings](https://peps.python.org/pep-0257/) and the [Google docstring style guide](https://google.github.io/styleguide/pyguide.html#38-comments-and-docstrings). ## Placement Docstrings let you document declarations at the point they appear in the source file. | Declaration | Position | |---------------------|-------------------------------------------| | Function or method | After the signature, before the body | | Struct or trait | After the opening line, before members | | Field or `comptime` | After the declaration, not before it | | Module or Package | First string in the file, after imports | Type, trait, and function docstrings use the same indentation level as the declaration. Field and `comptime` docstrings use the same indentation level as the field or `comptime` name. ```mojo struct Color: """Represents an RGB color.""" # Struct docstring var r: UInt8 """The red channel, in [0, 255].""" # Field docstring comptime MAX: UInt8 = 255 """The maximum value for any channel.""" # `comptime` docstring def to_hex(self) -> String: """Converts the color to a hex string.""" # Method docstring ... ``` Place module docstrings as the first string in the file. They describe the module's purpose, summarize its contents, and help documentation tools generate module-level reference pages. Place package docstrings in `__init__.mojo` files. They describe the package's purpose, summarize its exported modules, and provide package-level documentation. ## Summary line A docstring's first sentence is its summary. Summaries appear in index views and search results. | Declaration | Pattern | Examples | |---------------------|-----------------------------------|-------------------------------------------------------| | Function or method | Present-tense verb | `Clamps a value to the range [low, high].` | | | | `Converts a list of integers to a JSON array string.` | | Struct or trait | Noun phrase or present-tense verb | `A fixed-capacity circular buffer.` | | | | `Supports hashing to a fixed-size integer digest.` | | Field or `comptime` | Noun phrase | `The red channel, in [0, 255].` | Separate the summary from additional body text with a blank line. Start the summary with a capital letter and avoid repeating the declaration name. Prefer ending the summary with a period; the compiler also accepts `!`, `?`, or a closing backtick (see [Compiler checks](#compiler-checks)). ## Labeled content Structured sections include a labeled header followed by indented `name: Description.` entries. The doc generator automatically includes type information, so you don't need to repeat it in the description. The compiler validates some common structured sections against the declaration and warns about mismatches and missing entries: | Label | Documents | |----------------|-------------------------| | `Parameters:` | Compile-time parameters | | `Args:` | Runtime arguments | | `Returns:` | Return values | | `Raises:` | Error conditions | For example: ```mojo def resize[dtype: DType]( data: List[Scalar[dtype]], size: Int, fill: Scalar[dtype] = 0, ) raises -> List[Scalar[dtype]]: """Resizes a list by truncating it or padding it with a fill value. Parameters: dtype: The element type of the list. Args: data: The source list to resize. size: The target length. fill: The value used to pad the list when growing it. Returns: A new list with length `size`. Raises: An error if `size` is less than or equal to zero. """ ... ``` Mojo doesn't define a canonical set of section labels. Any `Label:` starts a new section. ### `Parameters` and `Constraints` Use `Parameters:` to document compile-time parameters when their role, behavior, or requirements are not obvious from the declaration. Use inline `Constraints:` clauses for simple parameter requirements: ```mojo Parameters: size: The static capacity. Constraints: Must be a power of two. dtype: The element type. Constraints: Must be a floating-point type. ``` Use a standalone `Constraints:` section for requirements that span multiple parameters, depend on the target architecture, or are not self-evident to most users: ```mojo def dot[size: Int]( a: SIMD[DType.float32, size], b: SIMD[DType.float32, size], ) -> Float32: """Computes the dot product of two SIMD vectors. Constraints: - `size` must be a power of two. - The target must support AVX2 or NEON. """ ... ``` ### `Args` Document each argument with its name and role. Mojo uses `Args` instead of `Arguments`. Indent continuation lines relative to the argument name: ```mojo Args: stride: The step between sampled indices. A stride of 1 returns all elements; a stride of 2 returns every other element. ``` ### `Examples` `Examples:` is not compiler-checked, but it is widely used in Mojo documentation. Use `Examples:` to show how to use an API in practice. It's normally the last section in a docstring. Example code is usually left-aligned with the label. Use fenced code blocks with the `mojo` language tag for syntax highlighting. ````text Examples for a hypothetical `find()` API: ```mojo var names: List[String] = ["alice", "bob", "carol"] print(names.find("bob").value()) # 1 print(names.find("dave") is None) # True var numbers: List[Int] = [1, 2, 3, 4, 3, 9, 3] while idx := numbers.find(3): _ = numbers.pop(idx.value()) print(numbers) # [1, 2, 4, 9] ``` ```` ### Custom labels Mojo supports custom section labels. The following are recommended conventions: | Label | Documents | |------------------|----------------------------------------------| | `Preconditions:` | Runtime conditions the caller must satisfy | | `Performance:` | Performance characteristics and tradeoffs | | `Safety:` | Safety requirements and undefined behavior | | `See:` | Related APIs, concepts, and references | Use `Preconditions:` for runtime conditions the caller must satisfy before calling, where a violation aborts the program (for example, a runtime assertion) rather than raising a catchable error. Choose among `Preconditions:`, `Constraints:`, and `Raises:` by how the condition is enforced: - `Preconditions:` for a runtime condition on the caller that aborts execution when violated and can't be caught. - `Constraints:` for a compile-time requirement that fails compilation when violated. - `Raises:` for a runtime condition that raises a catchable error. Use `Performance:` for complexity and for runtime behavior that is not obvious from complexity alone, such as allocation behavior, vectorization, scheduling, latency, I/O costs, or architecture-specific performance characteristics. Use `Safety:` for requirements, invariants, and operations that can lead to undefined behavior, memory safety issues, invalid references, or other unsafe states when used incorrectly. Use `See:` to link to related APIs, standards documents, algorithms, and external references. Mojo also accepts other labels, such as `Notes:`, but prefer putting that information in the docstring body rather than in a separate `Notes:` section. ### Section order Mojo doesn't enforce an order among sections, but a consistent order helps readers scan. Recommended order: `Parameters:` → `Args:` → `Returns:` → `Raises:` → `Preconditions:` → `Constraints:` → `Safety:` → `Performance:` → `See:` → `Examples:` Include only the sections that apply, and put `Examples:` last. ## Hidden elements ### `@doc_hidden` The `@doc_hidden` decorator excludes a declaration from generated documentation. The declaration still compiles normally but produces no documentation output. ```mojo @doc_hidden def _internal_helper(data: Pointer[UInt8, MutAnyOrigin]) -> Int: pass ``` Common uses include: - Hiding lifecycle and dunder methods not intended for direct use - Hiding implementation details such as private methods and helpers - Hiding deprecated internals kept for backward compatibility. See also: `@deprecated` ### Hidden example lines Prefix a line in a docstring example with `%#` to hide it from generated documentation. The line remains visible in the source file. `mojo doc` removes `%#` lines from generated output: ```mojo """ ... %# var result = format_result(0.857) print(result) # 85.7% ... """ ``` Common uses include: - Hiding setup code such as imports, helper functions, and temporary variables - Showing expected output in source examples without rendering it in generated documentation ## Inline formatting Docstrings support Markdown, inline code formatting, escape sequences, KaTeX syntax, and HTML tags. For example: | Content | Syntax | |-------------------------------------------------|---------------------------------------------------| | API names (types, functions, fields, arguments) | `` `Int` ``, `` `append()` ``, `` `pop(index)` `` | | Literal backslash in a code block | `\\\\` (renders as `\\`) | | Inline math | `$$x^y$$` | | Block math | `$$` on its own line, formula, `$$` | | Literal `$$` in text | `$$` | String escape sequences are honored everywhere, including inside code blocks. For example, `\n` produces a newline, while `\\t` produces the two-character sequence `\t` in a code example. KaTeX syntax supports mathematical notation in docstrings, including algorithms, formulas, and complexity annotations: - Double KaTeX backslashes: `\\frac`, `\\|`, `\\cdot` - Block formulas render centered; inline formulas render in text flow - `$$` is ignored inside backticks and fenced code blocks ## Compiler checks Mojo validates docstrings during compilation and reports common issues. For example: ```sh $ mojo optionalref.mojo optionalref.mojo:5:8: warning: doc string summary should begin with a capital letter or non-alpha character, but this begins with 'a' """an error type for when an empty `OptionalRef` is accessed""" ^ optionalref.mojo:5:8: warning: doc string summary should end with a period '.', exclamation mark '!', question mark '?', or backtick '`', but this ends with 'd' """an error type for when an empty `OptionalRef` is accessed""" ^ ``` `mojo doc` performs additional integrity checks. Use `--diagnose-missing-doc-strings` to report missing docstrings: ```sh $ mojo doc --diagnose-missing-doc-strings optionalref.mojo optionalref.mojo:1:1: warning: public module 'OptionalRef' is missing a doc string @fieldwise_init ^ optionalref.mojo:2:8: warning: struct takes parameters, but has no 'Parameters' in doc string struct EmptyOptionalRefError[T: Movable]( ^ ``` ### Validation modes Mojo uses two validation modes: - _Strict_ for public APIs - _Normal_ for private and internal declarations A declaration is public when it: - Doesn't start with `_` - Is not marked `@doc_hidden` - Is not synthesized - Is at module scope or a member of a public `struct` or `trait` These modes apply to both `mojo` and `mojo doc`. ### Missing docstrings ```sh mojo doc --diagnose-missing-doc-strings -Werror -o /dev/null stdlib/std/ ``` Reports public declarations without docstrings. `-Werror` converts warnings into errors. ### Universal checks These checks apply during compilation: ```sh mojo /path/to/file.mojo ``` **Section structure:** - Overindented section label - Duplicate section - Empty section **`Args:` and `Parameters:` entries:** - Entry names a missing argument or parameter - Duplicate entry - Entry out of declaration order - Missing entry description - Missing documented argument or parameter **`Returns:` and `Raises:` consistency:** - `Returns:` on a function without a return value - `Raises:` on a function that is not `raises` ### Strict mode checks Strict mode applies to public declarations. **Summary sentences, descriptions, and section body text:** - Must begin with a capital letter or non-alpha character - Must end with `.`, `!`, `?`, or `` ` `` This includes text in sections such as `Constraints:`, `Returns:`, and `Raises:`. ### Strict mode with `--diagnose-missing-doc-strings` This mode enables the strictest validation, such as in CI. Mojo will flag the following issues. **Missing docstrings:** - Public functions and methods - Public structs and traits - Public fields and `comptime` declarations - Public modules **Missing required sections on functions:** - `Args:` for functions with arguments - `Parameters:` for declarations with required parameters - `Returns:` for functions with return values - `Raises:` for `raises` functions **Missing required sections on non-functions:** - `Parameters:` for declarations with required parameters **Not checked:** - `Constraints:` - Custom labels such as `Notes:`, `Performance:`, and `Safety:` --- ## Mojo expression reference {/* VERIFIED: ParserExprs.cpp, ExprNode.h, ParserBase.h, ExprNodes.h */} An *expression* is any piece of code that produces a value. Expressions are the building blocks of computation: you combine them with operators, pass them as arguments, assign their results to variables, and use them as conditions in control flow. ## Identifier expressions An identifier refers to a named element: a variable, function, type, or module. Using an identifier in an expression gives you the thing it refers to: ```mojo score # a variable Int # a type range # a function ``` ## Parenthesized expressions Parentheses group subexpressions, overriding default precedence: ```mojo (a + b) * c # add a and b, multiply by c (x) # just x ``` Parentheses also let expressions span multiple lines without using backslash escapes: ```mojo var result = ( first_value + second_value + third_value ) ``` ## Tuples A *tuple* is a fixed-size, ordered group of values. Commas create tuples, not parentheses: ```mojo var a = 2, 3 # tuple without parentheses var b = (2, 3) # same tuple with parentheses var x, y = b # x is 2, y is 3 ``` Use a trailing comma to create a one-element tuple. Without it, `(1)` is just the integer `1` in parentheses: ```mojo () # empty tuple (1,) # one-element tuple (1, 2, 3) # three-element tuple ``` Tuples support indexing: ```mojo var point = (10, 20) print(point[0]) # 10 ``` ## Collection displays The compiler calls these *displays*. Displays are similar to literals, but unlike literals, displays can contain expressions as well as fixed values. Literals cannot contain expressions. For example: ```mojo [1, 2, 3] # list literal [1, 1+1, 1+1+1] # list display ``` ### Lists A *list display* creates a list from comma-separated values: ```mojo var empty: List[Float32] = [] var numbers = [1, 2, 3] var strings = ["one", "two", "three",] ``` Mojo allows trailing commas after all collection elements, including the final one. ### Dictionaries A *dict display* maps keys to values with `:` between each pair: ```mojo var empty: Dict[String, Int] = {} var ages = {"Alice": 30, "Bob": 25} ``` ### Sets A *set display* uses braces with values but no colons: ```mojo var primes = {2, 3, 5, 7} ``` Don't mix set and dict syntax. `{1, 2}` is a set. `{1: 2}` is a dict. ```mojo {"a": 1, 2} # Error: expected 'key: value' in dictionary expression {1, "b": 2} # Error: cannot have a 'key: value' pair in set initializer ``` **Sets are not initializer lists**. Brace syntax also serves as an *initializer list* that creates an instance of an inferred type. Without type context, the compiler can't distinguish a set from an initializer list, so the distinction is resolved at type-check time. Initializer lists can include positional values and keyword arguments: ```mojo {x, y} # set or initializer list, without context {z=4, "foo"} # initializer list with keyword argument ``` Initializer lists are syntactic sugar for initializer calls. `{1, "hello"}` is equivalent to `T(1, "hello")` when the type `T` is known from context. Use them for passing initialized instances as arguments: ```mojo process({1, "hello"}) # type inferred from signature var x: T = {} # type inferred from variable declaration, calls T() ``` **Sharp edge: set displays are core Mojo syntax but the `Set` type is not**. You must import `Set` from the standard library to use it as a type: ```mojo from std.collections import Set # Required to use Set type from std.testing import assert_equal def main() raises: var display_set = {1, 2, 3} # A set with elements 1, 2, and 3 assert_equal(len(display_set), 3) # The length of the set is 3 var empty_set = Set[Int]() # An empty set empty_set.add(4) # Add an element to the set empty_set.add(4) assert_equal(len(empty_set), 1) # Sets do not allow duplicate elements ``` ## Member access The dot operator accesses an attribute or method on a value: ```mojo var length = text.count() var x = point.x var name = person.name.upper() ``` Chaining is left to right: `a.b.c` accesses `c` on the result of `a.b`. ## Calls A *call expression* invokes a function or constructs a value by appending `()` to an expression: ```mojo print("hello") var result = compute(a, b) var p = Point(1.0, 2.0) ``` ### Positional and keyword arguments Arguments before any keyword argument are positional. Keyword arguments use `name=value` syntax. ```mojo def greet(name: String, loud: Bool = False): print(t"Hello, {name if not loud else name.upper()}!") greet("Alice") # Hello, Alice! greet("Alice", loud=True) # Hello, ALICE! greet(name="Bob") # Hello, Bob! ``` Positional arguments can't follow keyword arguments: ```mojo # greet(loud=True, "Alice") # Error: positional argument follows keyword argument ``` Keyword arguments can't be repeated: ```mojo greet(name="Alice", name="Bob") # Error: duplicate keyword argument 'name' ``` ## Subscripts and slices Square brackets after an expression look up a value by index(es) or key(s): ```mojo var item = collection[0] var value = mapping["key"] var cell = matrix[i, j] ``` ### Slices Colons inside square brackets create *slices*. Slices select a range of elements using `start:stop` or `start:stop:stride`: ```mojo var items = [0, 1, 2, 3, 4, 5] var first_three = items[0:3] # [0, 1, 2] (3 not included) var from_three = items[3:] # [3, 4, 5] var every_other = items[::2] # [0, 2, 4] var reversed = items[::-1] # [5, 4, 3, 2, 1, 0] ``` All three parts are optional. Start defaults to the beginning, stop defaults to the end, and stride defaults to 1. The element at the stop position isn't included in the result. ## Ternary conditional The `if`-`else` expression selects between two values based on a condition: ```mojo var label = "even" if x % 2 == 0 else "odd" ``` The condition follows `if`, and the alternate value follows `else`. If the condition is true, the expression evaluates to the first value. If false, the alternate. Ternary expressions are right-associative and can be chained: ```mojo var size = ( "small" if n < 10 else "large" if n > 100 else "medium" ) ``` This groups as `"small" if n < 10 else ("large" if n > 100 else "medium")`. ## Walrus operator Regular assignments (`=`) are statements, not expressions. They don't produce a value. The walrus operator (:=) is the expression form of assignment. It binds a value to a name and evaluates to that value. You can assign and use the result in a single step: ```mojo def main() raises: var items = List(range(20)) var n: Int if (n := len(items)) > 10: print(n) var name: String # declare before use while name := input("Prompt: "): # input is raising print("Hello,", name) ``` Strings are truthy. The loop ends when you press Return without entering text. The name must already be declared. Walrus only assigns into existing values. Walrus assignment is most useful for temporary values created as part of an expression. Its binding remains available for the rest of its scope. Walrus assignment doesn't imply ownership, reference, or memory semantics. Plus, the value on the right needn't exist in memory; it might exist only in a register. The walrus operator has the lowest precedence of any expression operator. Use parentheses when needed to make your intent clear: ```mojo if item := list[idx] < 50: # binds comparison result print(t"{item} is under 50") # "True is under 50" if (item := list[idx]) < 50: # binds list item print(t"{item} is under 50") # "(actual number) is under 50" ``` ## Compile-time expressions `comptime` forces an expression to evaluate at compile time. Parentheses are required: ```mojo def heavy_calculation() -> Int: var sum = 0 for i in range(1_000_000): sum += i return sum # var x = comptime heavy_calculation() # Error: requires parentheses var x = comptime(heavy_calculation()) # O(1) at runtime print(x) # 499999500000 ``` The loop runs once during compilation. At runtime, `x` is a constant. If the expression can't be evaluated at compile time, the compiler reports an error. Mojo also provides built-in expressions for compile-time type introspection. These look like function calls but they're keywords that operate on types and traits at compile time: ```mojo type_of(x) # type of an expression conforms_to(T, Trait) # test trait conformance origin_of(x) # origin of a reference ``` These three expressions are used for reflection, conditional type conformance, and origin sets. They return compiler-internal types and won't print. ## Function type expressions A function type expression describes the signature of a function as a type: ```mojo def() -> Int def(Int, Int) -> Int def(var value: String) -> None def() raises -> String def(T) -> T ``` Function type expressions can include argument types with conventions, return types, and effects like `raises`. ## Lambda expressions A `lambda` is an anonymous, single-expression function. Its arguments are parenthesized and typed, like a function declaration, and its body is a single expression with no `return`: ```text lambda [[parameter-list]] [(argument-list)] [effects] [{capture-list}] [-> ResultType] : expression ``` For example, this lambda returns its argument value incremented by 1: ```mojo var inc = lambda (x: Int) {} -> Int: x + 1 var y = inc(4) # 5 ``` For complete syntax, semantics, and examples, see the [lambda expressions reference](/docs/reference/lambda-expressions). ## Comprehension expressions A comprehension is a concise way to build a new collection by iterating over existing values and optionally filtering or transforming them. It replaces common loop-and-append patterns with a single expression. ### List comprehensions List comprehensions create lists: ```mojo var squares = [x * x for x in [0, 1, 2, 3, 4] if x % 2 == 0] # [0, 4, 16] var positive = [x for x in range(-3, 3) if x > 0] # [1, 2] ``` **Syntax:** `[expr for pattern in iterable if condition]` - Multiple `for` clauses create nested iteration - `if` clauses filter elements ### Set comprehensions Set comprehensions create sets. Sets don't store duplicates, so you may get fewer elements than iterations. For a Fibonacci generator that starts with `fib(0)=1` and `fib(1)=1`, the first 6 Fibonacci numbers are `1, 1, 2, 3, 5, 8`: ```mojo var fibs = {fib(x) for x in range(6)} # {1, 2, 3, 5, 8}, 5 elements from 6 iterations ``` **Syntax:** `{expr for pattern in iterable if condition}` ### Dictionary comprehensions Dictionary comprehensions create dictionaries: ```mojo var dict_squares = {x: x * x for x in range(3)} # {0: 0, 1: 1, 2: 4} var lengths: Dict[String, Int] = { k: len(k) for k in ["one", "two", "three", "four"] } # {one: 3, two: 3, three: 5, four: 4} ``` **Syntax:** `{key_expr: value_expr for pattern in iterable if condition}` ### Comprehension clauses Comprehensions support multiple `for` and `if` clauses: ```mojo var products = [ (x, y, x * y) for x in range(3) for y in range(3) if (x + y) % 2 == 0 ] # [(0, 0, 0), (0, 2, 0), (1, 1, 1), (2, 0, 0), (2, 2, 4)] ``` Clauses are evaluated left to right: - Each `for` introduces a new iteration variable - Each `if` filters based on the current values --- ## Mojo function declarations reference {/* VERIFIED: TokenKinds.def, Signatures.h, Signatures.cpp, ParserStmts.cpp, ParserBase.h, ParserBase.cpp, ASTDecl.h, ASTDecl.cpp, ExprNode.h, OverloadFitness.h, OverloadFitness.cpp, OverloadSet.h, OverloadSet.cpp, ParamInf.h, ParamInf.cpp, DeclResolution.cpp (return-type-only redefinition diagnostic). Consulted: ExprNodes.h, ExprNodes.cpp, ParserExprs.cpp, ASTType.h, MojoDiags.h */} {/* NOT VERIFIED: "Copy initializers can't raise" — likely enforced via trait/IR; no specific error string surfaced. "out arguments can't have defaults" — doc claim at line 365; no specific compiler error found, but plausible. "Copy ctor copy argument must use the default convention" — specific convention claim; didn't trace. "Import + local def with the same name" — verified by experiment to produce `invalid redefinition of '': cannot overload with this non-function definition`, not silent shadowing as the existing manual claims. Reference text was corrected accordingly. The aliased-import workaround was also confirmed to compile. */} A *function declaration* introduces a named, callable unit of code. Every function in Mojo starts with the `def` keyword: ```mojo def greet(name: String) -> String: return "Hello, " + name ``` The simplest function has a name, empty parentheses, and a body: ```mojo def do_nothing(): pass ``` ## Function names Function names must be valid identifiers. Backtick-escaped identifiers allow keywords as function names: ```mojo def `import`(): print("In `import`") def main(): `import`() # In `import` ``` ## Function signatures {/* VERIFIED: From ParserStmts.cpp, Signatures.h */} ```text def name(argument-list) -> ReturnType: body def name[parameter-list](argument-list) -> ReturnType: body def name(argument-list) raises -> ReturnType: body def name[parameter-list](argument-list) -> ReturnType where constraint: body def name[parameter-list](argument-list) raises -> ReturnType where constraint: body ``` A signature can include a name, a parameter list, an argument list, effects, a return type, and a `where` clause. Only the parentheses and the colon are required. *Arguments* are runtime values in parentheses. *Parameters* are compile-time values in square brackets. In other languages these are both called "parameters". Mojo distinguishes them to avoid confusion: ```mojo # T must be both `Comparable` (to test with `<` and `>`) and # `ImplicitlyCopyable` or you won't be able to return def clamp[T: Comparable & ImplicitlyCopyable]( val: T, lo: T, hi: T, ) -> T: if val < lo: return lo if val > hi: return hi return val ``` ## Markers Three markers divide parameter and argument lists into zones that control how callers pass values: | Marker | Arguments | Parameters | |--------|-----------------|-----------------| | `//` | No | Infer-only | | `/` | Positional-only | Positional-only | | `*` | Keyword-only | Keyword-only | Markers must appear in this order: `//`, then `/`, then `*`. Each can appear once. `/` can't be first in the list, and `*` can't be last. :::note Mojo markers follow Python's convention from PEP 570 and PEP 3102. ::: ### Infer-only marker (`//`, parameters only) `//` separates infer-only parameters from named parameters. The compiler deduces infer-only parameters from call-site arguments: ```mojo def inferred_type[T: Writable, //](value: T): print(t"Value is {value}. Type is {reflect[T].name()}.") def main(): inferred_type(5) # Value is 5. Type is SIMD[DType.int, 1]. inferred_type("Hello") # Value is Hello. Type is String. ``` Infer-only parameters can't be specified positionally: ```mojo # Error because 'inferred_type' got 1 positional parameter # but expected none. inferred_type[Int](5) ``` Keyword syntax bypasses this restriction: ```mojo inferred_type[T=Int](5) # OK: Value is 5. Type is Int. # Error because value passed to 'value' cannot be converted from # 'StringLiteral["Hello"]' to 'Int' inferred_type[T=Int]("Hello") ``` Inference isn't limited to infer-only parameters. With enough context, the compiler can infer named parameters too: ```mojo def add[T: Intable](a: T, b: T) -> Int: return Int(a) + Int(b) def main(): print(t"Sum is {add[Int](1, 2)}.") # Explicit T print(t"Sum is {add(1, 2)}.") # Inferred T print(t"Sum is {add[Float64](4.5, 1.2)}.") # Explicit print(t"Sum is {add(4.5, 1.2)}.") # Inferred ``` ### Positional-only marker (`/`) Everything before `/` is positional-only. Callers must pass these values by position, not by name: ```mojo def div(a: Int, b: Int, /): return a // b div(10, 3) # OK div(a=10, b=3) # Error ``` ### Keyword-only marker (`*`) Everything after `*` is keyword-only. Callers must pass values by name, whether parameters or arguments: ```mojo def configure(*, verbose: Bool, retries: Int): # ... configure(verbose=True, retries=3) # OK configure(True, 3) # Error ``` A `*args` variadic argument has the same effect on arguments that follow it: ```mojo def sum(*values: Int, name: String) -> Int: print(name, end=": ") var total = 0 for value in values: total += value return total def main(): print(sum(1, 2, 3, name="total")) # total: 6 # print(sum(1, 2, 3, "subtotal")) # Error because missing required keyword argument ``` ## Default values Arguments can have default values. Once a default appears, every following positional argument must also have one: ```mojo def connect( host: String = "www.modular.com", port: Int = 80, ): print(t"Connecting to {host}:{port}") def main(): connect() # Connecting to www.modular.com:80 connect(port=8080) # Connecting to www.modular.com:8080 ``` ```mojo def my_function(x: Int, y: Int = 0, z: Int = 0) -> Int: return x + y + z # Error because required positional argument follows optional # positional argument # def wrong(x: Int, y: Int = 0, z: Int): # return x + y + z ``` Keyword-only arguments are exempt from the ordering rule. They can mix required and optional freely: ```mojo def configure(*, retries: Int = 3, verbose: Bool): pass ``` Parameters also support defaults. ## Function constraints A `where` clause constrains compile-time parameters. It appears at the end of the declaration, after the return type (or after the argument list if there's no return type): ```mojo comptime LESS_THAN: Int32 = -1 comptime EQUAL: Int32 = 0 comptime GREATER_THAN: Int32 = 1 def compare[T: AnyType]( x: T, y: T, ) -> Int32 where conforms_to(T, Comparable): if x < y: return LESS_THAN elif x > y: return GREATER_THAN else: return EQUAL def main(): print(compare(5, 10)) # -1 (LESS_THAN) print(compare(7, 7)) # 0 (EQUAL) print(compare("Z", "A")) # 1 (GREATER_THAN) ``` `where` clauses can express complex constraints, such as limiting SIMD vector sizes to certain powers of 2: ```mojo def process[ n: Int, ](data: SIMD[.float32, n]) -> Float32 where ( n == 1 or n == 2 or n == 4 or n == 8 or n == 16 or n == 32 ): var sum: Float32 = 0.0 for i in range(n): sum += data[i] return sum def main(): var data = SIMD[.float32, 16](255.0) var sum = process[n=16](data) print(t"Sum: {sum}") # Sum: 4080.0 ``` `where` clauses belong at the end of a declaration: ```mojo # Correct: the `where` clause follows the signature. def correct[n: Int]() where n > 0: pass ``` A `where` clause inside a parameter list is invalid. Add it to the end of the declaration: ```mojo # Wrong: `where` is not allowed inside a parameter list. def wrong[n: Int where n > 0](): pass ``` A `where` clause in an argument list is invalid: ```mojo # Wrong: `where` clauses can only be used with compile-time parameters. def wrong(x: Int where x > 0): pass ``` A [`thin`](#thin) function *type* can carry its own trailing `where` clauses, constraining the parameters that type declares. See [Constrained function types](#constrained-function-types). ## Argument conventions An *argument convention* controls how an argument value passes to a function. It appears before the argument name. ### `mut` The caller's value is passed by mutable reference. Changes inside the function are visible to the caller: ```mojo def double_it(mut x: Int): x *= 2 ``` `mut` arguments can't have default values: ```mojo # Error because 'mut' arguments may not have defaults def wrong(mut x: Int = 0): pass ``` ### `var` The function receives an owned copy. If the caller transfers ownership with `^`, the original becomes inaccessible. Otherwise the value is copied and the caller keeps access: ```mojo def consume(var s: String): s += "!" print(s) def main(): var greeting = "Hello" consume(greeting) # Hello! (copied) print(greeting) # Hello consume(greeting^) # Hello! (moved) # print(greeting) # Error because uninitialized after move ``` ### `out` An `out` argument is the function's return slot. Only one `out` argument is allowed. It replaces the `->` return type: ```mojo def make_int(out result: Int): result = 42 def main(): var x = make_int() print(x) # 42 ``` A function can't use both `out` and `-> Type`: ```mojo # Error because function cannot have both an 'out' argument # and an explicit result type def wrong(out result: Int) -> Int: result = 0 ``` ### `deinit` The function takes ownership and destroys the value. Required for `self` in `__deinit__()` and the argument in move initializers: ```mojo struct Resource: var handle: Int def __deinit__(deinit self): _release(self.handle) ``` ### `ref` Passes a reference with an explicit *origin* specifier. The origin tracks where the reference came from: ```mojo def get_first[T: Copyable](ref data: List[T]) -> ref[data[0]] T: return data[0] def main(): var data: List[String] = ["one", "two", "three"] ref first = get_first(data) # mutable because `data` is mutable print(first) # one first = "Первый" print(data) # ['Первый', 'two', 'three'] ``` ### Default convention Without a convention, the argument is an immutable read-only reference. The caller keeps ownership: ```mojo def length[T: Copyable](s: List[T]) -> Int: return len(s) ``` ## Variadic arguments Variadic arguments accept a varying number of values ("indefinite arity"). Functions like print use variadic arguments to accept any number of values. ### Homogeneous variadics `*` before the argument name accepts any number of positional arguments of the same type (homogeneous arguments): ```mojo def sum_all(*values: Int) -> Int: var total = 0 for v in values: total += v return total ``` ### Variadic packs `*` before both the name *and* the type annotation creates a *variadic pack* that accepts arguments of different types (heterogeneous arguments): ```mojo def print_all[*Ts: Writable](*args: *Ts): comptime for idx in range(args.__len__()): print(args[idx], end=" ") print() def main(): print_all("Hello", 42, 3.14) # Hello 42 3.14 ``` :::note Why not `len(args)`? `args` has a `VariadicPack` type, and `VariadicPack.__len__()` is a `@staticmethod`, so `args.__len__()` is the same as `type_of(args).__len__()`. The compiler can evaluate it at compile time. `len(args)` doesn't work because `args` is a dynamic value, so `len(args)` is a dynamic expression. You can't use a dynamic value to drive a `comptime for`. ::: ### Variadic restrictions A function can have at most one `*args`. Variadic arguments can't have default values: ```mojo # Error because variadic arguments may not have defaults def wrong(*args: Int = 0): pass ``` `out` arguments can't be variadic: ```mojo def wrong(out *results: Int): pass ``` ## Function effects Effects appear after the closing parenthesis and before `->`. ### `raises` {#raises} Declares that the function can raise an error. An optional error type can follow `raises`: ```mojo def parse(text: String) raises -> Int: # ... def parse_strict(text: String) raises SomeError -> Int: # ... ``` A function can specify at most one error type after `raises`. ### `thin` {#thin} Used only in function *types*, `thin` indicates a function pointer type (not a closure) and ensures the function value doesn't capture values from its defining scope. Don't use `thin` in function declarations. ```mojo def map[ T: Copyable, U: Copyable ](f: def(T) thin -> U, input: List[T]) -> List[U]: # Used as type var result: List[U] = [] for item in input: result.append(f(item)) return result^ # `square` doesn't capture values. It can be passed to a thin type def square(x: Int) -> Int: return x * x def main(): var nums: List[Int] = [1, 2, 3] var squares = map(square, nums) print(squares) # Output: [1, 4, 9] ``` #### Constrained function types {#constrained-function-types} A `thin` function type that declares parameters can constrain them with trailing `where` clauses, written after the result type. The constraint is part of the type: callers must prove it holds for the parameters they bind. ```mojo comptime Kernel = def[w: Int](Int) thin -> None where ( w > 0, "width must be positive" ) def apply[F: Kernel](x: Int): F[4](x) # ok F[0](x) # error: violated constraint ``` Constraints are contravariant. A function satisfies the type when its own `where` clause is implied by the type's, or when it has none — the type promises its callers more than the function demands. A function that demands more than the type promises is rejected. Only `thin` function types accept a `where` clause. The clause binds to the innermost function type, so a declaration-level `where` that follows a function-type result needs that result parenthesized: ```mojo # The `where` clause constrains the returned function type. def inner_constraint[n: Int]() -> def() thin -> None where n > 0: ... # The `where` clause constrains `outer_constraint` itself. def outer_constraint[n: Int]() -> (def() thin -> None) where n > 0: ... ``` ### `abi("C")` {#abi-c} Declares that a function uses the C calling convention. Because C has no closure mechanism, `abi("C")` normally appears together with `thin` in function types: ```mojo # `add` is compiled with the C calling convention, so it can be called # from C or stored in a C-ABI function pointer. def add(a: Int32, b: Int32) abi("C") -> Int32: return a + b def main(): var fp: def(Int32, Int32) thin abi("C") -> Int32 = add print(fp(1, 2)) # 3 ``` :::caution Don't combine non-Mojo `abi()` effects with raising functions. Raising functions change calling conventions in non-obvious ways. The Mojo compiler: - accepts `def (String) abi("Mojo") raises` - rejects `def (String) abi("C") raises` `abi("Mojo")` is already the default. You don't need to specify it. ::: ## Return type `->` introduces the return type. It appears after any effects: ```mojo def square(x: Int) -> Int: return x * x ``` Without `->`, the function returns `None`. ## Special methods Certain method names have enforced signatures. The compiler checks argument count, conventions, and return types. ### Initializers An initializer must have an `out self` result: ```mojo struct Point: var x: Int var y: Int def __init__(out self, x: Int, y: Int): self.x = x self.y = y ``` Without `out self`, the compiler rejects the method: ```mojo # Error because __init__ method must return Self type # with 'out' argument def __init__(self): pass ``` ### Copy initializers A copy initializer uses a single keyword-only argument named `copy`: ```mojo def __init__(out self, *, copy: Self): self.x = copy.x self.y = copy.y ``` Copy a value with `Type.__init__(copy=value)`, `Type(copy=value)`, or `value.copy()`: - The `copy` argument must use the default convention of a readable immutable reference. - Copy initializers can't raise. - Trivial types can't define a copy initializer. - Conforming to `Copyable` or `ImplicitlyCopyable` automatically generates a copy initializer if one isn't already defined. - If the type isn't compatible with copying, the compiler rejects the conformance. Don't confuse copying with casting. `Type(value)` casts in Mojo. `Type(copy=value)` and `value.copy()` copy. Prefer the explicit `.copy()` call over the `copy` keyword argument form for idiomatic Mojo style. ### Move initializers A move initializer uses a single keyword-only argument named `move`: ```mojo def __init__(out self, *, deinit move: Self): self.x = move.x self.y = move.y ``` Call `Type.__init__(move=value)` or `Type(move=value)`: - The `move` argument must use the `deinit` convention. - Move initializers can't raise. - `RegisterPassable` types can't define a move initializer. They're always movable by copying a register. ### Deinitializers A deinitializer takes `deinit self`: ```mojo def __deinit__(deinit self): _release(self.handle) ``` - Deinitializers can't raise. - Trivial types can't define a deinitializer. You can call `value.__deinit__()` explicitly but the compiler calls it automatically at the value's last use, via ASAP destruction. Prefer custom deinitializers for [explicit destruction](/docs/manual/lifecycle/death/#explicitly-destroyed-types). ## Nested functions Functions can be defined inside other functions. Nested functions can capture values from the enclosing scope as *closures*: ```mojo def outer(x: Int) -> Int: def inner() {imm} -> Int: # Read-only references (`imm`) to outer scope return x + 1 return inner() ``` The compiler resolves nested function bodies immediately so captures bind correctly. ## Static methods `@staticmethod` makes a struct method callable without an instance. Static methods don't take `self`: ```mojo struct MathUtils: comptime pi: Float64 = 3.141592653589793 @staticmethod def square(x: Int) -> Int: return x * x def main(): print(MathUtils.square(5)) # 25 print(MathUtils.pi) # 3.141592653589793 ``` ## Function overloads {#function-overloads} {/* VERIFIED: OverloadFitness::isBetter in OverloadFitness.cpp, filterForBestCandidates in OverloadSet.cpp, ParamInf.cpp. "Cannot overload on return type only" diagnostic: DeclResolution.cpp around the `redefinition of function` emit. Raises-only redefinition behavior: verified by experiment to emit `redefinition of function '' with identical signature` from the same DeclResolution.cpp path; `raises` is not part of the signature for overload-set purposes. */} A *function overload* is one of two or more function declarations that share a name but differ in their signature. The compiler picks one of them at each call site. This is *static dispatch*: there's no runtime lookup. The choice is fixed when the call is type-checked. An *overload set* is the collection of overloads the compiler considers at a call site. It contains the declarations that share the same name in the same scope. Use overloads to give one operation more than one shape: different argument types, different argument counts, different keyword names, different `self` conventions, or different compile-time parameter signatures. ```mojo def add(x: Int, y: Int) -> Int: return x + y def add(x: String, y: String) -> String: return x + y def main(): print(add(1, 2)) # 3 print(add("Hi, ", "Mojo")) # Hi, Mojo ``` ### Where overload sets form Each scope builds its own overload set: - *Module scope.* Declarations with the same name in the same module form one overload set. - *Struct scope.* Methods on a struct (including `@staticmethod`) form one overload set per method name. - *Trait scope.* Required and provided methods on a trait form one overload set per method name. An overload set can't be extended across scopes. An import brings the name in as a non-function reference: you can't add another overload to it from your own module, and you can't redefine it. A local declaration that collides with an import produces an error. To avoid the error, use an alias: ```mojo from some_package import add as imported_add def add(x: Float64, y: Float64) -> Float64: return x + y # `add` resolves to the local definition. # `imported_add` resolves to the imported one. ``` ### What the compiler considers Overload resolution looks at: - The number, position, and keyword of each argument. - The type of each argument and each compile-time parameter. - The argument conventions on each argument. - Whether the candidate is an instance method or `@staticmethod`. - Whether a deinitializer is `@implicit`. Overload resolution doesn't look at the return type or any other context surrounding the call. ### Resolution rules The compiler discards every candidate whose signature can't be satisfied by the call. It then compares the remaining candidates pairwise. It applies the following rules in order until one wins. The compiler selects that candidate. 1. Pick the candidate that uses fewer implicit conversions between arguments and parameters. An empty match against an `*args` argument counts as an implicit conversion, so an exact match beats it. 2. Pick the candidate that doesn't bind non-empty variadic arguments. A signature without `*args` beats one whose `*args` argument receives at least one value. 3. Pick the candidate with fewer mismatched argument conventions. 4. Pick the candidate with a shorter parameter list. A function with no compile-time parameters beats one that declares a parameter. The parameter list also counts implicit parameters synthesized from argument types (for example, the unbound parameters of a `SIMD[...]` argument become implicit parameters on the function). After fitness comparisons, the compiler applies two tiebreakers: 5. Pick the candidate that is an instance method over a `@staticmethod` with the same name. 6. Pick the candidate that is a non-implicit deinitializer over an implicit one. If two candidates are equally good after these steps, the call is ambiguous. The compiler rejects it. :::note Rule 4 means a concrete function wins over a parameterized one. `def foo(a: Int)` beats `def foo[T: AnyType](a: T)` for a call with an `Int` argument, even though both signatures match. ::: ### Overloading parameters Functions can overload on compile-time parameters as well as on arguments: ```mojo def take_param[a: Int, b: Int](): print("take_param[a: Int, b: Int]") def take_param[a: Int, b: String](): print("take_param[a: Int, b: String]") def main(): take_param[1, 2]() # take_param[a: Int, b: Int] take_param[1, "hi"]() # take_param[a: Int, b: String] ``` ### Overloading the `self` convention A method can be overloaded by its `self` convention. The default call site uses an immutable reference for `self`, so `ref self` wins. To reach the `var self` overload, the caller transfers with `^` at the call site: ```mojo @fieldwise_init struct Counter(Copyable): var n: Int def which(ref self) -> Int: return 1 # Constrain `var self` to types where `^` actually transfers. # For `TrivialRegisterPassable` types, `^` is a no-op. This # overload would otherwise be silently unreachable. def which(var self) -> Int where not conforms_to( Self, TrivialRegisterPassable ): return 2 def main(): var c1 = Counter(0) print(c1.which()) # 1: default call uses an immutable self reference var c2 = Counter(0) print((c2^).which()) # 2: caller transfers self ``` :::caution Sharp edge For `TrivialRegisterPassable` types, the transfer operator `^` is a no-op. When using a trivial register type like `Int`, `Float64`, or similar, `(c^).which()` becomes `c.which()`. The compiler will warn you but won't reject your code. The compile-time conformance check in the `where` clause above makes a non-trivial constraint explicit. It filters the `var self` overload out for trivial register types instead of leaving it silently unreachable. ::: ### Instance methods beat static methods When both an instance method and a `@staticmethod` have the same name, a method-call expression picks the instance method (rule 5): ```mojo struct StaticOverload: def __init__(out self): pass def foo(mut self): print("instance method") @staticmethod def foo(): print("static method") def main(): var a = StaticOverload() a.foo() # instance method ``` To call the static method explicitly, use the type name: ```mojo StaticOverload.foo() # static method ``` ### Variadic candidates lose ties A signature without `*args` beats a variadic signature when both match the call, because the variadic version costs one implicit conversion for the empty pack (rule 1) or, with values supplied, loses on rule 2: ```mojo def take(x: Int): print("take(x: Int)") def take(*xs: Int): print("take(*xs: Int)") def main(): take(1) # take(x: Int) take(1, 2, 3) # take(*xs: Int): the only match. ``` ### Ambiguous calls When two candidates are equally good, the call fails: ```mojo struct MyString: @implicit def __init__(out self, s: String): pass struct YourString: @implicit def __init__(out self, s: String): pass def foo(name: MyString): print("MyString") def foo(name: YourString): print("YourString") def main(): # Error because the call is ambiguous: both overloads need exactly # one implicit conversion from `String` foo("Hello") ``` Resolve ambiguity by casting at the call site: ```mojo def main(): foo(MyString("Hello")) # MyString foo(YourString("Hello")) # YourString ``` Literals can trigger the same kind of ambiguity. An `IntLiteral` converts to both `Int` and `Float64` at equal cost, so a call like `take_param[1, 2]()` against the two overloads below is ambiguous: ```mojo def take_param[a: Int, b: Int](): pass def take_param[a: Int, b: Float64](): pass def main(): # Error because `IntLiteral` converts to both `Int` and `Float64` # at equal cost; the compiler can't pick a winner take_param[1, 2]() ``` To resolve, remove the ambiguity or remove the overload by renaming one version. ### Return types don't disambiguate Two overloads that differ only in their return type are indistinguishable to overload resolution: ```mojo def parse(s: String) -> Int: return 0 # Error because `parse` cannot overload on return type only; # the differing return type doesn't form a new overload def parse(s: String) -> Float64: return 0.0 ``` Use different argument types, an extra parameter, or a different function name instead. ### `raises` doesn't disambiguate Two functions that differ only in whether they `raises` have the same signature for overload-set purposes. The compiler rejects the second declaration: ```mojo def maybe_raise(x: Int) -> Int: return x # Error because `maybe_raise` already has this signature; # `raises` isn't part of the signature for overload purposes def maybe_raise(x: Int) raises -> Int: raise Error("nope") ``` If both behaviors are needed, give them distinct names or make the raising version take a different argument shape. ### Best practices - Use overloads when each version implements the same operation on a different shape of input. Don't overload to mean different things under the same name. - Don't rely on two implicit initializers being reachable from the same source type. Cast at the call site or make one initializer non-implicit. - Prefer overloading on argument *type* over overloading on convention. Convention-based overloads work, but they're easy to misread. - When an overload set should accept many types, write one parameterized function constrained with `where` instead of many near-duplicates. The compiler picks the concrete signature when both are present (rule 4). - Don't define a function with the same name as one you imported. The compiler rejects it. Import under an alias when you need both names. --- ## Mojo language reference The Mojo language reference provides a concise guide for Mojo's syntax, organized by grammar construct and designed for quick lookup when you need the exact rules or form of a language element. --- ## Mojo inline MLIR reference Mojo is built on [MLIR](https://mlir.llvm.org/) and exposes it directly to developers. When you need an operation that Mojo doesn't surface, such as hardware intrinsics, atomic memory orderings, or custom dialect operations, you can write the MLIR operation yourself instead of waiting for a language feature. MLIR (Multi-Level Intermediate Representation) is a compiler framework in the LLVM project. It models programs with custom, layered dialects that represent data flow, loops, and hardware-specific operations. These dialects are translated step by step into LLVM IR and then into machine code. ## Hello MLIR This example shows a minimal Mojo-MLIR program at the level of a "Hello World" implementation. It creates two MLIR index constants, adds them, and converts the result back to Mojo's `Int`: ```mojo def main(): var a: __mlir_type.index = __mlir_attr.`42 : index` var b: __mlir_type.index = __mlir_attr.`8 : index` var c = __mlir_op.`index.add`(a, b) print(Int(mlir_value=c)) # 50 ``` These built-ins work together: - `__mlir_type` sets the variable's MLIR type. - `__mlir_attr` provides a compile-time constant. - `__mlir_op` runs an MLIR operation. `Int(mlir_value=...)` converts the raw MLIR value back into Mojo. You could write `42 + 8` in plain Mojo. Inline MLIR gives you direct access to operations that Mojo doesn't expose yet, such as NVVM barriers, AMD matrix multiplies, and target-specific address spaces. ## The four built-in identifiers Mojo provides four built-in identifiers to reference MLIR from source code. Each corresponds to a common MLIR building block: | Built-in | Purpose | Produces | |---------------------|------------------------------|-------------------------| | `__mlir_type` | Reference an MLIR type | A type | | `__mlir_attr` | Reference an MLIR attribute | Compile-time value | | `__mlir_op` | Invoke an MLIR operation | Runtime value or `None` | | `__mlir_region` | Define a single-block region | Statement (no value) | You don't need to be an expert in MLIR basics to work through this page, but it helps to recognize a few core ideas: dialects, operations, attributes, types, and regions. This content focuses on how Mojo maps to those concepts. Types and attributes support two forms: dot/backtick syntax for simple names and bracket syntax for parameterized construction. ## `__mlir_type` The `__mlir_type` built-in lets you define MLIR types directly in Mojo. You can use these types in variable declarations, parameter lists, and `comptime` aliases, just as you'd use built-in types like `Float64` or `Pointer`. ### Dot and backtick syntax For simple type names that are valid identifiers, use dot syntax. Use backticks when the name includes special characters like `!`, `<`, or `>`: ```mojo var x: __mlir_type.i1 # 1-bit integer var y: __mlir_type.index # Machine-width index var z: __mlir_type.f64 # 64-bit float var a: __mlir_type.`!kgen.none` # Dialect type with ! prefix var b: __mlir_type.`!kgen.scalar` # Pop dialect scalar var c: __mlir_type.`!kgen.pointer>` # Nested pointer ``` MLIR uses short names for primitive types. Here's what you'll see most often: | MLIR name | Meaning | |-----------|---------------------------------------------| | `i1` | 1-bit integer (boolean) | | `i8` | 8-bit signless integer | | `i32` | 32-bit signless integer | | `i64` | 64-bit signless integer | | `si32` | 32-bit signed integer | | `si64` | 64-bit signed integer | | `ui32` | 32-bit unsigned integer | | `f16` | 16-bit float (IEEE half) | | `bf16` | 16-bit bfloat | | `f32` | 32-bit float | | `f64` | 64-bit float | | `index` | Machine-width integer for sizes and offsets | "Signless" means the type itself doesn't specify signed or unsigned. The operation using the value decides how to interpret it. The `s` and `u` prefixed variants (`si32`, `ui64`) carry signedness in the type. For a full list, see [MLIR's Builtin Types documentation](https://mlir.llvm.org/docs/Dialects/Builtin/#types). Dialect-defined MLIR types use the `!` prefix. Without it, the compiler rejects the type with an error like: ```text invalid MLIR type: kgen.dtype ``` Use `__mlir_type.`!kgen.dtype`` instead. Here's a runnable example that declares MLIR-typed variables, assigns values with `__mlir_attr`, and converts them back to Mojo for printing: ```mojo def mlir_types_in_action(): var flag: __mlir_type.i1 = __mlir_attr.true var count: __mlir_type.index = __mlir_attr.`0 : index` # Convert back to Mojo types to print print(Bool(flag)) # True print(Int(mlir_value=count)) # 0 ``` ### Bracket syntax Use bracket syntax when you need to build a type from values known at compile time. The compiler splices Mojo expressions into an MLIR type string. The following list builds a single MLIR type string. It alternates between backtick literals (copied as-is) and Mojo expressions (inserted as MLIR text): ```mojo # From SIMD: build storage type from dtype and size parameters. # For SIMD[DType.float32, 4], produces: !kgen.simd<4, f32> comptime _mlir_type = __mlir_type[ `!kgen.simd<`, Self.size._mlir_value, `, `, Self.dtype._mlir_value, `>` ] # From Pointer: produces, for example, !kgen.pointer comptime _mlir_type = __mlir_type[`!kgen.pointer<`, Self.T, `>`] # From Optional: produces, for example, !kgen.variant comptime _mlir_type = __mlir_type[ `!kgen.variant<`, Self.T, `, i1>` ] # Nested substitution: produces complex var complexInt: __mlir_type[ `complex<`, __mlir_type.i32, `>` ] ``` Bracket lists only accept positional operands. Using keyword operands produces a compile-time error. ### MLIR types in parameter lists You can use MLIR types as compile-time parameters for structs and functions. Dialect types like `!kgen.string` and `!kgen.dtype` appear here alongside builtin types. ```mojo # Parameter is a compile-time MLIR string (for example, "hello") struct StringLiteral[value: __mlir_type.`!kgen.string`]: # ... # Parameter is a compile-time dtype (for example, f32 or si64) def example[dtype: __mlir_type.`!kgen.dtype`](): # For dtype=f32, produces: !kgen.scalar var a: __mlir_type[`!kgen.scalar<`, dtype, `>`] # ... ``` ### Properties of raw MLIR types Raw MLIR types (not wrapped in a struct) are register-passable, trivially copyable, and trivially movable. They don't have methods or attributes and accessing `.field` on the type produces an error. ## `__mlir_attr` Use `__mlir_attr` to define MLIR attributes in Mojo. An MLIR attribute is a compile-time constant embedded in the IR. ### Dot and backtick syntax Use dot syntax for simple attribute names. Use backticks when you need full MLIR literal syntax: ```mojo # Boolean attributes __mlir_attr.true __mlir_attr.false # Built-in type shorthands used in return positions __mlir_attr.i1 __mlir_attr.index __mlir_attr.f16 __mlir_attr.f32 __mlir_attr.si32 # Typed constants (with backtick syntax) __mlir_attr.`0 : index` __mlir_attr.`42 : i17` __mlir_attr.`1 : si32` ``` MLIR attribute constants use MLIR literal syntax, not Mojo's. Binary (`0b1010`), octal (`0o17`), and hex (`0xFF`) prefixes aren't supported. Use decimal values instead. ```mojo # Dialect-specific attributes __mlir_attr.`#kgen.dtype.constant : !kgen.dtype` # DType constant for float32 __mlir_attr.`#index` # Signed less-than predicate __mlir_attr.`#pop` # Sequential consistency ordering __mlir_attr.`#kgen.simd<"nan"> : !kgen.scalar` # Float32 NaN constant ``` Here's a runnable example that uses MLIR attributes as constants in a computation. This computes an approximate circle area using integer arithmetic: ```mojo def circle_area_approx(radius: Int) -> Int: """Approximate area using integer math: pi ≈ 3.""" var r = radius.__mlir_index__() var r_squared = __mlir_op.`index.mul`(r, r) var pi: __mlir_type.index = __mlir_attr.`3 : index` var area = __mlir_op.`index.mul`(pi, r_squared) return Int(mlir_value=area) def main(): print(circle_area_approx(5)) # 75 print(circle_area_approx(10)) # 300 ``` ### Bracket syntax Use bracket syntax when you need to build an attribute from compile-time values. This follows the same rules as `__mlir_type`. The following list builds a single MLIR attribute string. It alternates between backtick literals (copied as-is) and Mojo expressions (inserted as MLIR text): ```mojo # String concatenation at compile time. # For "Hello" + "World", produces: # #pop.string_concat<"Hello","World"> : !kgen.string __mlir_attr[ `#pop.string_concat<`, self.value, `,`, rhs.value, `> : !kgen.string`, ] # Null pointer constant for a parameterized type. # For Pointer[Int, MutAnyOrigin], produces: # #interp.pointer<0> : !kgen.pointer __mlir_attr[`#interp.pointer<0> : `, Self._mlir_type] # Compile-time parameter expression. # For a=5, produces: #kgen.param.expr : index comptime new_lower = __mlir_attr[ `#kgen.param.expr : index` ] ``` ## `__mlir_op` Use `__mlir_op` to call MLIR operations directly from Mojo. This lets you use operations that Mojo doesn't expose yet, like hardware intrinsics and dialect-specific operations. ### Syntax Place compile-time parameters (attributes) in square brackets and runtime values (operands) in parentheses. Put the operation name in backticks: ```mojo __mlir_op.`dialect.operation`(operands) __mlir_op.`dialect.operation`[attributes](operands) ``` ### Operations with no attributes When an operation only needs operands (runtime values), call the operation directly: ```mojo # Boolean XOR __mlir_op.`pop.xor`(self._mlir_value, rhs._mlir_value) # Index addition __mlir_op.`index.add`(self._mlir_value, rhs._mlir_value) # Trap (no operands, no result) __mlir_op.`llvm.intr.trap`() ``` ### Operations with attributes An operation may need to pass attributes, the key-value pairs in square brackets before the operands (that is, the runtime values): ```mojo # Cast a pop scalar to a builtin i1 # (for example, !kgen.scalar → i1) __mlir_op.`pop.cast_to_builtin`[ _type=__mlir_type.i1 ](mlir_value) # Signed less-than comparison on two index values. Returns i1. __mlir_op.`index.cmp`[ pred=__mlir_attr.`#index` ](self._mlir_value, rhs._mlir_value) # Load a value from a pointer with atomic ordering. # Returns a value of the pointer's element type. __mlir_op.`pop.load`[ ordering=ordering.__mlir_attr(), _type=Self._mlir_type, ](ptr.address) ``` Here's a runnable example that combines comparisons with `pop.select` to clamp a value into a range: ```mojo def clamp(val: Int, low: Int, high: Int) -> Int: """Clamp val to [low, high] using MLIR comparisons and select.""" var v = val.__mlir_index__() var lo = low.__mlir_index__() var hi = high.__mlir_index__() # If val < low, use low. # index.cmp returns i1, but pop.select needs a !kgen.scalar. var too_low = __mlir_op.`pop.cast_from_builtin`[ _type=__mlir_type.`!kgen.scalar` ]( __mlir_op.`index.cmp`[pred=__mlir_attr.`#index`]( v, lo ) ) var result = __mlir_op.`pop.select`(too_low, lo, v) # If result > high, use high var too_high = __mlir_op.`pop.cast_from_builtin`[ _type=__mlir_type.`!kgen.scalar` ]( __mlir_op.`index.cmp`[pred=__mlir_attr.`#index`]( result, hi ) ) result = __mlir_op.`pop.select`(too_high, hi, result) return Int(mlir_value=result) def main(): print(clamp(15, 0, 10)) # 10 print(clamp(-5, 0, 10)) # 0 print(clamp(7, 0, 10)) # 7 ``` ### Special attributes Three attributes have special meaning to the compiler: | Attribute | Purpose | |---------------|----------------------------------------------------------------------| | `_type` | Sets the result type; pass `None` for an operation with no result | | `_properties` | Passes MLIR operation properties as a `DictionaryAttr` | | `_region` | References a named `__mlir_region` as a region argument | ### `_type` Most operations require an explicit result type: ```mojo # Single result type var i1Cast = __mlir_op.`index.castu`[ _type=__mlir_type.i1 ](idxConstant) ``` When an operation returns multiple values, assign them to a typed tuple: ```mojo # Returns the current source location as (line, column, filename). # _properties passes inline depth to the code generator. _ = __mlir_op.`kgen.source_loc`[ _type = ( __mlir_type.index, __mlir_type.index, __mlir_type.`!kgen.string` ), ]() ``` Operations that produce no result take `_type=None`: ```mojo # Fence after mbarrier initialization. Guarantees the barrier # object is fully constructed before any thread uses it. __mlir_op.`nvvm.fence.mbarrier.init`[_type=None]() ``` If the compiler can't infer the result type and you don't provide `_type`, you'll receive an error: `unable to infer result type from MLIR operation 'name'`. ### `_properties` Some MLIR operations store configuration in properties instead of attributes. Attributes are compile-time constants in Mojo. Properties store compile-time metadata on the operation. Pass them as a `DictionaryAttr`: ```mojo # Returns current source location as (line, column, filename). # _properties passes inline depth to the code generator. _ = __mlir_op.`kgen.source_loc`[ _type = ( __mlir_type.index, __mlir_type.index, __mlir_type.`!kgen.string` ), _properties = __mlir_attr.`{inlineCount = 1 : i64}`, ]() ``` Operations can mix attributes and properties in the same bracket list: ```mojo # 64-bit integer addition with "no signed wrap" overflow checking. # nsw means undefined behavior on signed overflow, enabling # optimizations. __mlir_op.`llvm.add`[ _type=__mlir_type.i64, _properties=__mlir_attr.`{ overflowFlags = #llvm.overflow }`, ](arg0, arg1) ``` As an example, NVVM operations often use the `operandSegmentSizes` property to describe which optional operands are present: ```mojo # Async bulk copy from global to shared cluster memory. # operandSegmentSizes: dst(1), src(1), size(1), mbar(1), # cache_hint(0), predicate(1). __mlir_op.`nvvm.cp.async.bulk.shared.cluster.global`[ _properties=__mlir_attr.`{ operandSegmentSizes = array }`, _type=None, ](dst, src, size, mbar, predicate) ``` In Mojo, you can only call registered MLIR operations. If you try to call one that isn't registered, the compiler will error with: `use of unregistered MLIR operation 'name'`. ## `__mlir_region` Use `__mlir_region` to define a block of MLIR code for an operation to run. A region is a block of code that an MLIR operation runs, similar to a loop body or callback. Unlike a closure, it doesn't implicitly capture variables. Some MLIR operations take a region as input. You define the block with `__mlir_region` and pass it to the operation using the `_region` attribute. A region is written as a named block with arguments and an indented body: ```mojo __mlir_region name(arg: type, ...): body ``` ### Basic usage Some MLIR operations accept a region argument, a block of code that the operation controls. You define the region with `__mlir_region` and connect it to the operation with the `_region` attribute. The following example uses `hlcf.loop`, an MLIR loop operation. It repeatedly runs the region body, passing the current iteration value as an argument. The region calls `hlcf.continue` with the next value: ```mojo comptime one = __mlir_attr.`1 : index` def structured_for_loop() -> __mlir_type.index: # Define the loop body as a region. The operation passes # the current iteration value as `i`. __mlir_region loop_body(i: __mlir_type.index): # Yield the next iteration value: i + 1 __mlir_op.`hlcf.continue`( __mlir_op.`index.add`(i, one) ) # Start at 0, run loop_body repeatedly, # return the final value. return __mlir_op.`hlcf.loop`[ _type=__mlir_type.index, _region=__mlir_attr.`"loop_body"`, ](__mlir_attr.`0 : index`) ``` The region arguments (`i` in this example) come from the operation that uses the region. Here, `hlcf.loop` passes the current loop value as `i`. The `_region` attribute takes the region name as a string. This loop runs indefinitely. To exit conditionally, wrap `hlcf.break` in `hlcf.if`. In practice, it's simpler to use Mojo's `for` and `while` loops for control flow. The following example uses a Mojo `while` loop for control flow and MLIR operations for the computation inside. The loop condition uses an MLIR comparison, and the body uses MLIR arithmetic to update the accumulator and counter: ```mojo def sum_to(end: Int) -> Int: """Mojo while loop with MLIR arithmetic and comparison.""" var acc: __mlir_type.index = __mlir_attr.`0 : index` var i: __mlir_type.index = __mlir_attr.`0 : index` var one: __mlir_type.index = __mlir_attr.`1 : index` # end.__mlir_index__() unwraps Mojo Int to raw __mlir_type.index while Bool(__mlir_op.`index.cmp`[ pred=__mlir_attr.`#index` ](i, end.__mlir_index__())): acc = __mlir_op.`index.add`(acc, i) i = __mlir_op.`index.add`(i, one) return Int(mlir_value=acc) def main(): print(sum_to(10)) # 45 print(sum_to(0)) # 0 print(sum_to(1)) # 0 print(sum_to(5)) # 10 ``` ### Multiple regions in one scope A function can define multiple regions, each with its own name. This example defines a region and passes it to `co.suspend`, an MLIR coroutine operation that suspends execution and later resumes by running the provided region: ```mojo @always_inline def _suspend_async[ body: def(AnyCoroutine) capturing -> None ](): # Runs when the coroutine resumes. # The operation passes the coroutine handle as `hdl`. __mlir_region await_body( hdl: __mlir_type.`!co.routine` ): body(hdl) # Signal that the await body is done __mlir_op.`co.suspend.end`() # Suspend the current coroutine, registering await_body # as the code to run when it resumes. __mlir_op.`co.suspend`[_region="await_body".value]() ``` Operations that accept multiple regions reference them by name. Each `__mlir_region` defines a single block. ### Region arguments Region arguments look like function arguments, but they don't support Mojo argument conventions like `ref`, `var`, or `mut`. The operation provides the argument values directly as raw MLIR values: ```mojo # Region arguments receive raw MLIR values from the enclosing # operation. Mojo conventions don't apply. __mlir_region my_region( x: __mlir_type.index, # Raw index from the operation y: __mlir_type.`!kgen.scalar`, # Raw f32 scalar ): # x and y are raw MLIR values, not Mojo types. # Wrap them (for example, Int(mlir_value=x)) to use # Mojo operations on them. # ... ``` ## Common dialects These dialect prefixes appear frequently in the stdlib and kernel code: | Prefix | Covers | |-----------|-----------------------------------------------------------| | `pop.*` | Mojo portable ops: arithmetic, casts, SIMD, pointers | | `index.*` | Index-typed arithmetic and comparisons | | `kgen.*` | Codegen primitives: structs, variants, parameters | | `lit.*` | Language-level ops: ownership, references, closures | | `llvm.*` | LLVM dialect: traps, inline assembly, pointer ops | | `nvvm.*` | NVIDIA GPU intrinsics: barriers, async copies, tensor ops | | `co.*` | Coroutine ops: suspend, resume, destroy, await | The `pop`, `kgen`, `co` and `lit` dialects are internal implementation details of the compiler and may change without notice. Built-in dialects, like `index`, are also available. ## Stdlib patterns The standard library uses inline MLIR in consistent patterns that show up across the codebase. These patterns will help you understand how to use the built-ins in your own code. They serve as a reference for common use cases. ### Wrapper structs The most common pattern: a Mojo struct wraps a raw MLIR type in a field called `_mlir_value`. The struct provides a Mojo-friendly interface; the field holds the actual MLIR representation. `Bool` wraps a single bit: ```mojo # Bool wraps a single MLIR bit. The struct provides Mojo-level # operators; the i1 field holds the actual hardware value. struct Bool: var _mlir_value: __mlir_type.`!kgen.scalar` # 1-bit integer: true or false def __init__(out self, value: __mlir_type.`!kgen.scalar`): self._mlir_value = value # Store the raw bit directly ``` When the storage type depends on struct parameters, define it as a `comptime` alias. For example, `SIMD` builds its type from `dtype` and `size`: ```mojo # SIMD builds its storage type at compile time from its parameters. # For SIMD[DType.float32, 4], _mlir_type produces: !kgen.simd<4, f32> struct SIMD[dtype: DType, size: Int]: comptime _mlir_type = __mlir_type[ `!kgen.simd<`, Self.size._mlir_value, `, `, Self.dtype._mlir_value, `>` ] var _mlir_value: Self._mlir_type # Parameterized SIMD vector ``` This pattern appears in `SIMD`, `Pointer`, `Tuple`, `Variant`, and `Optional`'s internal storage. Here's a complete, runnable wrapper struct. `Counter` wraps an MLIR index, exposes `increment` and `value` methods, and converts back to Mojo for printing: ```mojo struct Counter: """A simple counter backed by a raw MLIR index.""" var _mlir_value: __mlir_type.index def __init__(out self): self._mlir_value = __mlir_attr.`0 : index` def increment(mut self): var one: __mlir_type.index = __mlir_attr.`1 : index` self._mlir_value = __mlir_op.`index.add`( self._mlir_value, one ) def value(self) -> Int: return Int(mlir_value=self._mlir_value) def main(): var c = Counter() c.increment() c.increment() c.increment() print(c.value()) # 3 ``` ### Operations as methods Once a struct wraps an MLIR type, its methods delegate to MLIR operations. The `_mlir_value` field goes in, the result comes back, and the struct re-wraps it: ```mojo # From Bool: operators delegate to pop operations on the raw i1. # XOR with true flips the bit: ~False → True, ~True → False def __invert__(self) -> Bool: return __mlir_op.`pop.xor`( self._mlir_value, __mlir_attr.true ) # Bitwise AND on the two underlying i1 values def __and__(self, rhs: Bool) -> Bool: return __mlir_op.`pop.and`( self._mlir_value, rhs._mlir_value ) ``` For `Int`, the `index` dialect operations produce raw index values. `Int(mlir_value=...)` wraps them back: ```mojo # From Int: add two raw index values, wrap the result back into Int def __add__(self, rhs: Int) -> Int: return Int( mlir_value=__mlir_op.`index.add`( self._mlir_value, rhs._mlir_value ) ) # Signed less-than comparison, returns i1 (auto-wraps to Bool) def __lt__(self, rhs: Int) -> Bool: return __mlir_op.`index.cmp`[ pred=__mlir_attr.`#index` ](self._mlir_value, rhs._mlir_value) ``` Here's the `Counter` struct from the previous example extended with an `__add__` operator, showing how the pattern applies to custom types: ```mojo struct Counter: """A counter with addition, backed by a raw MLIR index.""" var _mlir_value: __mlir_type.index def __init__(out self): self._mlir_value = __mlir_attr.`0 : index` def __init__(out self, *, mlir_value: __mlir_type.index): self._mlir_value = mlir_value def increment(mut self): var one: __mlir_type.index = __mlir_attr.`1 : index` self._mlir_value = __mlir_op.`index.add`( self._mlir_value, one ) def __add__(self, rhs: Counter) -> Counter: return Counter( mlir_value=__mlir_op.`index.add`( self._mlir_value, rhs._mlir_value ) ) def value(self) -> Int: return Int(mlir_value=self._mlir_value) def main(): var a = Counter() a.increment() # 1 a.increment() # 2 var b = Counter() b.increment() # 1 var c = a + b print(c.value()) # 3 ``` --- ## Mojo identifiers, keywords, and conventions reference {/* VERIFIED: TokenKinds.def, Lexer.cpp, Signatures.h, Signatures.cpp, ExprNode.h, ParserExprs.cpp, ParserStmts.cpp, ParserBase.h */} {/* In TokenKinds.def, `_` is declared with TOK_KEYWORD rather than TOK_PUNCTUATION. This is an internal classification used by the lexer; for the language surface, `_` is a regular identifier character (see the identifier grammar below). Do not document `_` as a keyword. */} Every Mojo source file is built from tokens. This page covers the tokens you choose (identifiers), the tokens the language reserves (keywords), and the context-sensitive tokens that control how values are passed and bound (conventions). ## Identifiers Every time you declare a variable, define a function, or create a struct, you give it a name. That name is an *identifier*. Identifiers are how you refer to things in your code: `count` in `var count = 0`, `Point` in `struct Point`, `greet` in `def greet()`. This section covers what makes a valid identifier. ### Regular identifiers A regular identifier starts with a letter or underscore, followed by any combination of letters, digits, and underscores: ```text identifier → [a-zA-Z_][a-zA-Z0-9_]* ``` Such as: ```mojo foo _private MyStruct basic_value ``` Identifiers are case-sensitive. `MyStruct` and `mystruct` are different names: ```mojo var MyStruct = 1 var mystruct = 2 assert_equal(MyStruct, 1) # passes assert_equal(mystruct, 2) # passes ``` ### Escaped identifiers An *escaped identifier* is enclosed in backticks. Backticks allow any characters except vertical whitespace and backticks themselves: ```mojo `struct` # Use a keyword as a name `日本語の変数` # Non-ASCII identifier `my value` # Spaces in a name ``` Escaped identifiers are useful when calling into external code that uses a Mojo keyword as a name, or when writing identifiers in natural language. Empty backtick identifiers are not allowed. ## Keywords *Keywords* are reserved words with fixed meaning. They cannot be used as ordinary identifiers (use an escaped identifier if you need to). ### Control flow These keywords control which code runs and in what order. | Keyword | Purpose | |------------|-----------------------------------------| | `if` | Conditional execution | | `elif` | Additional condition in an `if` chain | | `else` | Default branch in conditionals or loops | | `for` | Iteration loop | | `while` | Conditional loop | | `break` | Exits the innermost loop | | `continue` | Skips to the next loop iteration | | `pass` | No-op placeholder statement | | `return` | Returns from a function | | `with` | Context manager statement | ### Error handling These keywords structure error propagation and recovery. | Keyword | Purpose | |-----------|-------------------------------------------------------| | `try` | Begins an error-handling block | | `except` | Error handler clause | | `finally` | Always-execute clause in a `try` block | | `raise` | Raises an error | | `assert` | Aborts if a condition is false (gated by `-D ASSERT`) | ### Declarations These keywords introduce functions, types, and bindings. | Keyword | Purpose | |----------|------------------------------------------| | `def` | Function declaration | | `lambda` | Anonymous single-expression function | | `struct` | Struct type declaration | | `trait` | Trait declaration | | `var` | Scoped variable binding | | `ref` | Scoped reference binding | ### Keyword operators Five operators are spelled as words rather than symbols. Symbolic operators (`+`, `-`, `*`, `^`, `//`, etc.) are punctuation, not keywords. | Keyword | Purpose | Keyword | Purpose | |---------|---------------|---------|-----------------| | `and` | Logical AND | `or` | Logical OR | | `not` | Logical NOT | `in` | Membership test | | `is` | Identity test | | | ### Imports These keywords control module imports. | Keyword | Purpose | |----------|------------------------------------------| | `import` | Imports a module | | `from` | Selective import from a module | | `as` | Aliasing in imports and `except` clauses | ### Compile-time | Keyword | Purpose | |------------|--------------------------------| | `comptime` | Forces compile-time evaluation | ### Literal keywords These keywords are also literals. They produce a value directly. | Keyword | Value | Keyword | Value | |---------|---------------------------------|---------|--------------------| | `True` | boolean true | `False` | boolean false | | `None` | Absence of a value (`NoneType`) | `Self` | The enclosing type | ### Case sensitivity All keywords are case-sensitive: - `True` is a keyword; `true` is not. - `None` is a keyword; `none` is not. - `Self` is a keyword; `self` is a conventional argument name, not a keyword. ## Conventions *Conventions* tell the compiler how values are passed and how bindings are created. Convention names aren't reserved, so existing Python code that uses these names won't break, but in Mojo signatures they have fixed meaning. **Argument conventions** appear before argument names in function signatures. They control ownership and mutability: {/* markdownlint-disable MD013 */} | Convention | Role | Meaning | |------------|----------------------|-------------------------------------------------------------| | (`imm`) | Argument | Immutable reference to an existing value (default behavior) | | `mut` | Argument | Mutable reference to an existing value | | `out` | Argument | Returns a value without a return arrow | | `deinit` | Argument | Destructive transfer; end of a value's lifecycle | | `var` | Argument or variable | Independent mutable owned copy of the value | | `ref` | Argument or variable | Reference that doesn't own the value | {/* markdownlint-enable MD013 */} `var` and `ref` also appear in **variable declarations**, where `var` creates a scoped mutable variable and `ref` creates a scoped reference binding: ```mojo struct CountingTool: var value: Int def __init__(out self): # out: self is the return value self.value = 0 def increment(mut self): # mut: modifies self in place self.value += 1 var count = 0 # var creates a scoped mutable variable var data: List[Int] = [1, 2, 3] ref view = data # ref binds a reference, no copying ``` A `self` argument without a convention is an immutable reference. Modifying it requires `mut`: ```mojo struct CountingTool: # continuing from above... def get(self) -> Int: self.value += 1 # Error: self is immutable return self.value ``` `out` declares the return value by name. It can't be combined with `->`: ```mojo def make_point(out result: Point): # OK result = Point(0, 0) def make_point(out result: Point) -> Point: # Error: function cannot have result = Point(0, 0) # both an 'out' argument and # an explicit result type ``` `raises` and `where` also have fixed meaning in declarations. `raises` declares that a function can raise errors. `where` introduces a constraint clause at the end of a declaration: ```mojo def validate(value: Int) raises: if value < 0: raise Error("must be non-negative") def process[T: Copyable](value: T) where conforms_to(T, Sized): # T must implement Sized to be used here ``` --- ## Mojo lambda expressions reference {/* Compiler verified: 2026-08-05, Mojo 1.0.0.dev0 (modular b6e7701fac1) Last updated: 2026-08-05 (new page); 2026-08-06 (polish, clarity) VERIFIED: ParserExprs.cpp (parseLambda, lambda_expr grammar comment, kw_lambda in canParseAtom, unparenthesized-args diagnostic), ExprNodes.h / ExprNodes.cpp / ExprNodePrinters.cpp (LambdaNode), DeclResolution.cpp (LambdaNode::emitIR: isThin test = bodyCaptures.values.empty() && node->captures.empty() && !node->captureAllByConvention, plus the input-param singleton test; promoteClosure fold; emitClosureInstance fallback; "cannot use a capturing lambda" diagnostics), Signatures.cpp (parseCaptureList, ParsedCaptureList, captureAllByConvention), mblib2to3/Grammar.txt (lambdef, old_lambdef), mblack/nodes.py (space after `lambda`). All runnable examples on this page execute in docs/code/reference/lambda-expressions/tests.mojo; the rejected forms were each confirmed against the compiler individually. */} A *lambda* is an anonymous, single-expression function. It has no name, its body is a single expression, and it doesn't use `return`. Lambda expressions are commonly passed to other functions as arguments, making them useful for higher-order programming, callbacks, event handlers, and other localized behavior. In Mojo, lambda expressions are part of the function declaration family: ```mojo def main(): var inc = lambda (x: Int) -> Int: x + 1 print(inc(4)) # 5 ``` This lambda is equivalent to the following function declaration: ```mojo def inc(x: Int) -> Int: return x + 1 print(inc(4)) # 5 ``` Lambdas complement `def` functions. Use `def` for named, reusable functions and lambdas for short, inline behavior. :::caution Lambda expressions are under active development. Return-type inference isn't implemented. If you omit the return type, the lambda returns `None`, even when its body produces a value. The rules on this page describe Mojo's current behavior. ::: ## Syntax Lambda expressions use a compact syntax. Most parts are optional, depending on what the lambda captures, accepts, and returns. Every lambda includes the `lambda` keyword, a body expression, and the `:` that introduces it: ```text lambda [[parameter-list]] [(argument-list)] [effects] [{capture-list}] [-> ResultType] : expression ``` The simplest lambda takes no arguments, captures nothing, and returns `None`: ```text lambda: None ``` Each part follows the same convention as standard functions: ```mojo def main(): # Fully explicit. Uses an empty "no-capture" capture list `{}` var a = lambda (x: Int) {} -> Int: x + 1 # Parameterized var b = lambda [T: Intable](x: T) -> Int: Int(x) + 1 var y = 1 # Capture list omitted. `y` defaults to `imm` var c = lambda (x: Int) -> Int: x * 2 + y # Return type omitted (`None`) var list: List[Int] = [1] var d = lambda (x: Int) {mut list}: list.append(x) # Arguments and return type omitted. Mutable capture var e = lambda {mut list}: list.append(0) print(a(4), b(4)) # 5 8 ``` ## Arguments Each argument must appear in parentheses and have a type. Types can be concrete (`String`) or parameterized (`T`, `Self.U`): ```mojo # Concrete argument var hello = lambda (x: String) {} -> String: "Hello, " + x # Parameterized argument var inc = lambda [T: Intable](x: T) -> Int: Int(x) + 1 ``` Omit the argument list for lambdas that don't take arguments: ```mojo var no_args = lambda -> Int: 42 ``` ### Argument conventions Arguments use the same conventions as functions. The default convention is `imm`, which captures an immutable reference: ```mojo def main(): var read_arg = lambda (x: Int) {} -> Int: x + 1 # Same as: var read_arg = lambda (imm x: Int) {} -> Int: x + 1 var own_arg = lambda (var x: Int) {} -> Int: x + 1 var mut_arg = lambda (mut x: Int) {}: x.__iadd__(1) var list: List[Int] = [1, 2, 3] mut_arg(list[0]) print(list) # [2, 2, 3] ``` ### Variadic arguments Lambda expressions support `*args` and `**kwargs`, separately or together. `**kwargs` packs into an `OwnedKwargsDict`, so declare it with `var`: ```mojo def main(): var count = lambda (*args: Int) {} -> Int: len(args) var named = lambda (var **kwargs: Int) {} -> Int: len(kwargs) print(count(10, 20, 30)) # 3 print(named(a=1, b=2)) # 2 ``` ## Return types When omitted, return types default to `None`: ```mojo lambda: 5 # Error: can't convert IntLiteral to None lambda (x: Int) {}: x + 1 # Error: can't convert Int to None ``` ## Lambda closures and capture lists A lambda becomes a *closure* when it carries state from its enclosing scope or binds the parameters it declares at each call site. Lambda capture lists use the same [conventions](/docs/reference/closure-declarations/#capture-conventions) as nested `def` closures: `imm`, `mut`, `ref`, `var`, plus copyable and movable. A lambda becomes a closure under these circumstances: - The lambda body references a value from the enclosing scope. ```mojo var z = 10 var f = lambda (x: Int) -> Int: x + z # `z` is captured print(f(5)) # 15 ``` In the absence of an explicit capture list, the default capture convention used here is an immutable reference (`imm`). - The lambda body uses an explicit capture convention. ```mojo var list: List[Int] = [1, 2, 3] var f = lambda (x: Int) {mut list}: list.append(x) # `list` is captured f(10) print(list) # [1, 2, 3, 10] ``` Using `{mut}` produces the same result, but `{mut list}` is more precise. It limits captures to `list`. `{mut}` captures every outer value used in the body. Explicitly naming captures turns accidental references into errors instead of silent captures. - The lambda declares its own parameter list. ```mojo # N is lambda-owned, bound at each call var f = lambda [N: Int](x: Int) {} -> Int: x + N print(f[5](3)) # 8 ``` Parameters declared by an enclosing scope are compile-time substituted, not captured: ```mojo def total_as_ints[T: Intable & Copyable](args: List[T]) -> Int: var to_int: def(v: T) thin -> Int = lambda (v: T) -> Int: Int(v) var total = 0 for ref a in args: total += to_int(a) return total def main(): print(total_as_ints([1.5, 2.5, 3.9])) # 6 ``` An empty capture list (`{}`) means "capture nothing." It excludes the default `imm` convention, so using a variable from an enclosing scope is an error: ```mojo var z = 10 var f = lambda (x: Int) {} -> Int: x + z # Error: z isn't captured ``` Any explicit capture convention makes a lambda a closure, even when it captures nothing. For example, `lambda (x: Int) {imm} -> Int: x + 1` is a closure, while the same lambda with the capture list omitted is *thin*. ## Thin lambdas {#thin} A lambda that isn't a closure is `thin`. To be thin, a lambda captures nothing, uses no explicit capture conventions, and doesn't declare its own parameters. Thin lambdas can be: - Used as a thin function pointer, including `abi("C")` callbacks. - Passed as a thin function-type parameter. - Bound to a symbol with `comptime`. - Returned from a function. - Stored in a struct field. - Used as a default argument or parameter value. Thin matters when a lambda must outlive the scope that created it or is needed at compile time. ### C ABI boundaries Thin lambdas can cross C ABI boundaries because they don't carry runtime state: ```mojo var fp = lambda (a: Int32, b: Int32) abi("C") -> Int32: a + b print(fp(1, 2)) # 3 ``` The `abi("C")` effect must appear on the lambda declaration. It's not enough to type the variable. If you choose to use explicit typing, the type must *also* carry the `abi("C")` effect. ### Compile-time use You can call any lambda directly. You can also pass any lambda as a runtime argument to higher-order functions with function-shaped infer-only parameter types: ```mojo def hof[T: def(x: Int) -> Int, //](f: T): # ... ``` Compile-time parameters are different. Unless a parameter is typed as `thin`, you can't pass lambdas at compile time. When it *is*, the lambda must be thin. Mojo can execute thin lambdas at compile time, and assign the result to a comptime name: ```mojo def main(): comptime whole = (lambda (x: Int) {} -> Int: x * 2)(21) print(whole) # 42 ``` ## Effects Place lambda effects after the argument list and before the capture list: ```mojo def apply_raising(f: def(x: Int) raises thin -> Int, arg: Int) raises -> Int: return f(arg) def main() raises: print(apply_raising(lambda (x: Int) raises -> Int: x + 1, 2)) # 3 ``` Every lambda body is a single expression and can't contain a `raise` statement. Lambdas only raise by calling something else that raises. Declaring `raises` allows the lambda to propagate exceptions, not raise them. ## Nesting Each lambda body can contain another lambda expression. The inner one captures the outer one's arguments through its own capture list, and references the outer one's parameters directly: ```mojo def main(): var f = lambda (x: Int) {} -> Int: ( lambda (y: Int) {imm x} -> Int: y + x )(3) print(f(6)) # 9 ``` ## Restrictions Lambda expressions have the following restrictions: - **Single expression**: The body is one expression. There's no `return`, no statement body, and no multi-statement form. - **No return-type inference**: An omitted return type is `None`, not a solved type. - **Arguments need types**: There's no argument-type inference from the use site. - **No `thin` in the signature**: `thin` applies to function *types*, not to lambda declarations. So long as the lambda doesn't declare any parameters that must be supplied from call sites, adding the `{}` capture list ensures the lambda is thin. ## Errors {/* markdownlint-disable MD013 */} | Compiler complaint | Trigger | |-----------------------------------------------|-------------------------------------------------------------------------------------| | Can't convert value to `None` in return value | Returns a value from a lambda with no return type | | Could not infer capture convention | Excludes capture conventions (`{}`) but references a value from the enclosing scope | | Mutating method on an immutable value | Mutation through an `imm` capture | | Capturing lambda in comptime initializer | A lambda closure bound to a `comptime` name | | Capturing lambda in type parameter | A lambda closure passed to a type parameter | | Capturing lambda in default parameter | A lambda closure used as a default parameter value | | Can't implicitly convert to a `thin` type | A lambda closure passed where a thin function is required | {/* markdownlint-enable MD013 */} --- ## Mojo literals reference A *literal* is a value written directly in source code: `42`, `"hello"`, `True`. Literals produce values without reading variables or calling functions. Each section below covers one literal type, its syntax, and any rules the lexer enforces. :::note Materialization makes a compile-time value available at runtime. Integer, floating-point, and string literals are implicitly materialized to their respective runtime types (Int, Float and String). ::: ## Integer literals *Integer literals* represent whole numbers in four bases: ```mojo 42 # Decimal 0xFF # Hexadecimal (0x or 0X prefix) 0o52 # Octal (0o or 0O prefix) 0b101010 # Binary (0b or 0B prefix) ``` Integer literals follow these lexical rules: ```text integer → decinteger | bininteger | octinteger | hexinteger decinteger → nonzerodigit ("_" | digit)* | "0"+ ("_" | "0")* bininteger → "0" ("b" | "B") ("_" | bindigit)+ octinteger → "0" ("o" | "O") ("_" | octdigit)+ hexinteger → "0" ("x" | "X") ("_" | hexdigit)+ ``` Integer literals are always non-negative. `-1024` is the unary negation operator `-` applied to the literal `1024`. Underscores can appear between digits for readability. Mojo is more permissive than Python here. Consecutive and trailing underscores are allowed: ```mojo 1_000_000 # Readable grouping 1__000_ # Also valid (consecutive and trailing underscores OK) ``` Leading zeros in decimal literals are not allowed. Use the `0o` prefix for octal: ```mojo 0123 # Error: leading zeros in decimal integer literals are not permitted 0o123 # OK: octal ``` A base prefix must be followed by at least one digit: ```mojo 0x # Error: no digits specified for hex literal 0b # Error: no digits specified for binary literal 0o # Error: no digits specified for octal literal ``` ## Floating-point literals *Floating-point literals* represent numbers with a fractional or exponent part: ```mojo 1.0 3.14159 .5 # Fraction only (no integer part) 2. # Integer part with decimal point 2.5e-3 # With exponent 1E10 # Capital E works too ``` Floating-point literals follow these lexical rules: ```text floatnumber → pointfloat | exponentfloat pointfloat → digitpart? fraction | digitpart "." exponentfloat → (digitpart | pointfloat) exponent fraction → "." digitpart exponent → ("e" | "E") ("+" | "-")? digitpart digitpart → digit ("_" | digit)* ``` Floating-point literals are always non-negative. `-3.14` is the unary negation operator `-` applied to the literal `3.14`. When included, an exponent marker (`e` or `E`) must be followed by at least one digit: ```mojo 2.5e # Error: expecting a digit after the exponent 2.5e- # Error: expecting a digit after the exponent 2.5e-3 # OK ``` Underscores in floating-point literals work as they do in integers. Place them anywhere that enhances readability: ```mojo 1_000.000_5 ``` ## String literals *String literals* represent text values. Mojo supports single and double quotes, and a triple-quote form for multi-line strings: ```mojo "Hello" 'world' """Multi-line string """ # Includes final newline '''Also multi-line''' # Includes 4 spaces at the start of the second line ``` Triple-quoted strings include any newlines literally. A backslash at the end of a line suppresses the newline, joining the next line directly: ```mojo """\ This string has no leading newline.""" ``` String literals on adjacent lines are joined into a single string. This works on one line or across lines when the continuation is indented: ```mojo var x = "Hello, " "World" # "Hello, World" var y = "line one " "line two" # "line one line two" (indented continuation) ``` Prefix with `r` or `R` to create a *raw string* that disables escape processing: ```mojo r"C:\path\to\file" # Backslashes treated literally ``` ### Escape sequences Mojo recognizes these escape sequences in non-raw string literals: | Sequence | Meaning | Sequence | Meaning | |----------|-----------------------------------|--------------|-----------------------------------| | `\\` | Backslash | `\a` | Bell | | `\"` | Double quote | `\b` | Backspace | | `\'` | Single quote | `\f` | Form feed | | `\n` | Newline | `\v` | Vertical tab | | `\r` | Carriage return | `\xHH` | Hex value (exactly 2 hex digits) | | `\t` | Tab | `\0`–`\377` | Octal value (1–3 octal digits) | | `\uHHHH` | Unicode code point (4 hex digits) | `\UHHHHHHHH` | Unicode code point (8 hex digits) | Mojo source files are UTF-8. String literals may contain non-ASCII characters directly. ```mojo var wave = "👋" ``` Non-ASCII characters can also be written as Unicode hex escapes: ```mojo var wave = "\U0001F44B" # 8-digit hex escape, 👋 var euro = "\u20AC" # 4-digit hex escape, € ``` - `\uHHHH` accepts code points from U+0000 to U+FFFF - `\UHHHHHHHH` accepts the full Unicode range, U+0000 to U+10FFFF Both forms reject surrogate code points (U+D800 to U+DFFF), which are reserved for UTF-16 encoding. Code points above U+FFFF require `\U`, not a UTF-16 surrogate pair. ## T-string literals *T-string literals* support expression interpolation using `{}`: ```mojo var name = "World" var greeting_template = t"Hello, {name}!" # "Hello, World!" var result_template = t"1 + 1 = {1 + 1}" # "1 + 1 = 2" ``` Expressions inside `{}` are evaluated at runtime. Adjacent t-string literals are joined, just like regular string literals. To use them as strings except in print statements, cast them to `String`: ```mojo var name = "Alice" var greeting = t"Hello, {name}!" # Type is T-string print(greeting) # Prints "Hello, Alice!" var greeting_str = String(greeting) # Convert to regular String ``` T-strings can be triple-quoted and combined with the raw prefix (any case combination of `r`/`R` and `t`/`T`, in either order): ```mojo t""" Hello, {name}! """ rt"Path: {base}\subdir" # Raw t-string: backslashes are literal ``` Use `{{` and `}}` to include literal braces in a t-string: ```mojo t"Use {{braces}} in t-strings" # "Use {braces} in t-strings" ``` T-strings can be nested. An interpolation expression can itself contain t-strings, up to 20 levels deep. ```mojo var name = "world" var greeting = t"Hello, {t"dear {name}"}!" print(greeting) # "Hello, dear world!" ``` ## Boolean literals `True` and `False` represent boolean truth values. ```mojo var x = True var y = False ``` ## None literal `None` represents the absence of a value. It's the only value of type `NoneType`. ```mojo var x: NoneType = None ``` A function without an explicit return type returns `None`. These two declarations are equivalent: ```mojo def greet(): print("hello") def greet() -> None: print("hello") ``` ## Self literal `Self` refers to the enclosing type inside a struct or trait definition: ```mojo from std.math import sqrt @fieldwise_init struct Point: var x: Float64 var y: Float64 @staticmethod def create() -> Self: # Self refers to Point return Self(0.0, 0.0) def distance(self) -> Float64: # self is an argument name, not Self return sqrt(self.x ** 2 + self.y ** 2) ``` `Self` (capital S) is a keyword that refers to the type. `self` (lowercase) is a conventional argument name for the instance. ## Discard pattern The underscore `_` discards a value in an assignment: ```mojo _, var y = get_pair() # Ignore the first element ``` ## Ellipsis literal `...` marks a trait method as required. Conforming types must provide their own implementation. It's only valid inside trait definitions: ```mojo trait Drawable: def draw(self) -> None: ... # Required: conforming types must implement ``` `...` and `pass` aren't interchangeable. `pass` is a no-op statement that provides an empty body. `...` is a requirement marker that means "you must implement this." --- ## Mojo numeric types reference Mojo's numeric primitives are built on `SIMD` vectors. Every fixed-width numeric type is a one-element `SIMD` called a `Scalar`. The `DType` specifies the kind of values stored in a `SIMD` vector, such as `int`, `uint`, `float32`, `int64`, or `uint8`. Mojo provides sized and unsized integer types, floating-point types, and `Byte` for raw byte data. ## `SIMD` {#simd} `SIMD` stands for "Single Instruction, Multiple Data". It lets the CPU operate on multiple values at once using a single instruction. A `SIMD` value stores one or more values of the same type in a fixed-size vector. The number of values is called the *width*, and it must be a power of two. The width is part of the type. For example, `SIMD[.float32, 4]` is a vector of four 32-bit floats. `SIMD[.int8, 16]` is a vector of sixteen 8-bit integers. When the first `SIMD` parameter expects a `DType` value, you can write `.float32` instead of `DType.float32`. The same shorthand works in `.cast[.int32]()` and other APIs that take a `DType` argument. When a `SIMD` value holds one value, it behaves like a scalar. When it holds several, operations apply to all values at once: ```mojo var v = SIMD[.float32, 4](1.0, 2.0, 3.0, 4.0) var doubled = v * 2.0 # All four elements doubled print(doubled) # [2.0, 4.0, 6.0, 8.0] ``` Modern CPUs can process 4, 8, 16, or more values in parallel with SIMD, which can significantly improve performance over scalar operations. :::note `SIMD` has a hard limit of 2**15 (32768) elements. This is a compile-time limit, not a runtime one. In practice, the usable width is much smaller and depends on the hardware. For example, `SIMD[.float32, 4]` fits in a 128-bit register, while `SIMD[.float32, 16]` requires 512 bits, which matches or exceeds the width of most SIMD registers. Always benchmark to find the optimal width for your workload and target hardware. ::: ### Element access Read and write individual elements by index ("*lane*"): ```mojo v[0] # Read element 0 → Scalar[.float32] v[0] = 5.0 # Write element 0 ``` ### Operations Arithmetic, comparison, and bitwise operations apply to all elements at once: ```mojo var a = SIMD[.float32, 4](1.0, 2.0, 3.0, 4.0) var b = SIMD[.float32, 4](5.0, 6.0, 7.0, 8.0) var sum = a + b # [6.0, 8.0, 10.0, 12.0] var prod = a * b # [5.0, 12.0, 21.0, 32.0] ``` Reductions combine all elements into a single value: ```mojo a.reduce_add() # 10.0 a.reduce_max() # 4.0 a.reduce_min() # 1.0 ``` Casting converts each element to a different numeric type. The number of elements stays the same, even when the target type is wider or narrower: ```mojo var a = SIMD[.float32, 4](1.0, 2.0, 3.0, 4.0) var ints = a.cast[.int32]() # [1, 2, 3, 4] var wide = a.cast[.float64]() # 4 × Float64 var tiny = a.cast[.float16]() # 4 × Float16 ``` Clamping restricts elements to a range. Both bounds are inclusive, so the result can equal the bounds: ```mojo # max(min(self, upper_bound), lower_bound) a.clamp(1.5, 3.5) # [1.5, 2.0, 3.0, 3.5] ``` `min()` and `max()` are free functions, not methods: ```mojo min(a, b) # Element-wise minimum max(a, b) # Element-wise maximum ``` ## `Scalar` {#scalar} A `SIMD` with one element is called a `Scalar`. Every fixed-width numeric name in Mojo is a `Scalar` alias: ```mojo # These are all the same type var a: Scalar[.float32] = 3.14 var b: Float32 = 3.14 var c: SIMD[.float32, 1] = 3.14 ``` When you write `Float32`, you're writing `Scalar[.float32]`, which is `SIMD[.float32, 1]`. ## `DType` specifications {#dtype} `DType` names the kind of values stored in a `SIMD` vector, such as `float32`, `int64`, or `uint8`. A `DType` doesn't store data. It tells `SIMD` how to interpret each element and which operations to use: ```mojo # DType selects a number kind, such as 32-bit float or 8-bit integer var x: SIMD[.float32, 4] = # ... # four 32-bit floats var y: SIMD[.int8, 16] = # ... # sixteen 8-bit ints ``` Use `DType` to write functions that work across numeric kinds: ```mojo # Double a value. The cast is required because the parameterized type # parameter can't be used directly with the literal `2`. def double[T: DType](x: Scalar[T]) -> Scalar[T]: return x * UInt8(2).cast[T]() ``` ### Integer DType specifications In contexts that expect a `DType` value, write the contextual form (for example `.int32` instead of `DType.int32`). The tables list every member: | Signed | Width | Unsigned | Width | |-----------|---------|--------------|---------| | `.int8` | 8-bit | `.uint8` | 8-bit | | `.int16` | 16-bit | `.uint16` | 16-bit | | `.int32` | 32-bit | `.uint32` | 32-bit | | `.int64` | 64-bit | `.uint64` | 64-bit | | `.int128` | 128-bit | `.uint128` | 128-bit | | `.int256` | 256-bit | `.uint256` | 256-bit | | `.int` | Machine | `.uint` | Machine | ### Floating-point DType specifications | Value | Selects | |--------------------|----------------------------| | `.float16` | 16-bit IEEE half | | `.bfloat16` | 16-bit brain float | | `.float32` | 32-bit IEEE single | | `.float64` | 64-bit IEEE double | | `.float8_e4m3fn` | 8-bit (4-exp, 3-mantissa) | | `.float8_e4m3fnuz` | 8-bit, unsigned zero | | `.float8_e5m2` | 8-bit (5-exp, 2-mantissa) | | `.float8_e5m2fnuz` | 8-bit, unsigned zero | | `.float8_e8m0fnu` | 8-bit (8-exp, no mantissa) | | `.float4_e2m1fn` | 4-bit (2-exp, 1-mantissa) | ### Other DType specifications | Value | Selects | |---------|-----------------| | `.bool` | Boolean (1-bit) | ## Integers ### The unsized `Int` type {#int} `Int` is Mojo's default integer. When you write `var x = 42`, you assign an `Int`. It's the type behind loop counters, collection indices, and `len()` results: ```mojo def main(): var a: Int = 42 comptime a_type = reflect[type_of(a)].name() print("a:", a_type) # a: SIMD[DType.int, 1] ``` `Int` matches the hardware's native word size. Under the hood it wraps the machine's index register directly, which is why it's the natural choice for counting and addressing. `Int` is 64-bit on most platforms today, but that isn't guaranteed. Code that depends on a specific width should use a sized type. `Int` is equivalent to `Scalar[.int]` and `SIMD[.int, 1]`. ### Integer-type bounds `Int` exposes its bounds as compile-time constants: | Constant | Value | |----------------|---------------------------------| | `Int.MAX` | Maximum representable value | | `Int.MIN` | Minimum representable value | ```mojo print(Int.MIN) # -9223372036854775808 print(Int.MAX) # 9223372036854775807 ``` All integer types offer `MAX` and `MIN` as well: | Constant | Value | |----------------------|-----------------------------| | `.MAX` | Maximum representable value | | `.MIN` | Minimum representable value | For example: ```mojo print(UInt.MIN) # 0 print(UInt.MAX) # 18446744073709551615 print(UInt8.MAX) # 255 print(Int8.MIN) # -128 print(UInt32.MAX) # 4294967295 print(Int32.MIN) # -2147483648 print(SIMD[.int16, 1].MIN) # -32768 ``` ### `UInt` {#uint} `UInt` is a machine-width unsigned integer: ```mojo def main(): var b: UInt = 42 comptime b_type = reflect[type_of(b)].name() print("b:", b_type) # b: SIMD[DType.uint, 1] ``` ### Sized integer types Sized integer types have a declared width that stays the same on every platform. | Signed | Width | Unsigned | Width | |----------|---------|-----------|---------| | `Int8` | 8-bit | `UInt8` | 8-bit | | `Int16` | 16-bit | `UInt16` | 16-bit | | `Int32` | 32-bit | `UInt32` | 32-bit | | `Int64` | 64-bit | `UInt64` | 64-bit | | `Int128` | 128-bit | `UInt128` | 128-bit | | `Int256` | 256-bit | `UInt256` | 256-bit | Each is an alias for a one-element `SIMD`. For example, `Int32` is `Scalar[.int32]`, which is `SIMD[.int32, 1]`. The unsigned types follow the same pattern. **Using sized vs unsized integers**: - Use `Int` and `UInt` for counts, indices, loop bounds, and general-purpose math. It's what the standard library expects and returns. - Use sized integers when width matters: file layouts, pixel data, hardware registers, or any context where the number of bits is part of the contract. - Use named types for scalar work and `SIMD` when you need vectors. ```mojo var general = 42 # Int (machine width) var small: UInt8 = 255 var large: Int64 = -9_000_000_000 var pair = SIMD[.uint32, 2](10, 20) # a 2-element vector ``` ### `Byte` {#byte} `Byte` is another name for `UInt8`: ```mojo var buf: List[Byte] = [0x48, 0x65, 0x6C, 0x6C, 0x6F] ``` Use `Byte` when the data represents raw bytes rather than small numbers. It's the element type used in many I/O and memory interfaces. ## Floating point types Mojo doesn't provide a `Float` type analogous to `Int`. Instead it provides numerous fixed-width floating-point types. Each is an alias for a one-element `SIMD`: | Type | Bits | Standard | What it is | |-------------------|-----------|-------------------|-----------------------------------------------| | `Float16` | 16 | IEEE 754 binary16 | `Scalar[.float16]` | | `Float32` | 32 | IEEE 754 binary32 | `Scalar[.float32]` | | `Float64` | 64 | IEEE 754 binary64 | `Scalar[.float64]` | | `BFloat16` | 16 | Brain float | `Scalar[.bfloat16]` | | `Float4_e2m1fn` | 4 | OCP MX | `Scalar[.float4_e2m1fn]` | | `Float8_e4m3fn` | 8 | OFP8 | `Scalar[.float8_e4m3fn]` | | `Float8_e4m3fnuz` | 8 | -- | `Scalar[.float8_e4m3fnuz]` | | `Float8_e5m2` | 8 | OFP8 | `Scalar[.float8_e5m2]` | | `Float8_e5m2fnuz` | 8 | -- | `Scalar[.float8_e5m2fnuz]` | | `Float8_e8m0fnu` | 8 | OFP8 §5.4 | `Scalar[.float8_e8m0fnu]` | | `FloatLiteral` | arbitrary | -- | Compile-time only. Materializes to `Float64`. | :::note - IEEE-754 is the IEEE Standard for Floating-Point Arithmetic. - OFP8 is an 8-bit Floating Point Specification, which creates a standard for representing floating-point numbers in a compact format. ::: ### `Float16` {#float16} 16-bit IEEE 754 half-precision. The motivation is throughput and memory bandwidth: half the storage of `Float32` means twice the values fit in registers and cache, and GPU tensor cores process it at higher throughput. 1 sign bit, 5 exponent bits, 10 mantissa bits. The narrower exponent range limits dynamic range to roughly ±65504. Values beyond that overflow to infinity; very small values underflow to zero. This makes `Float16` workable for inference but less ideal for training, where gradients can span many orders of magnitude. Use `BFloat16` for training instead. `Float16` is natively accelerated on GPUs. On CPU, it requires ARM FP16 extension or Intel AVX-512 FP16. Other CPUs fall back to software emulation. ### `Float32` {#float32} 32-bit IEEE 754 single-precision. 23 mantissa bits give roughly 7 significant decimal digits; 8 exponent bits cover a range from roughly 1e-38 to 3.4e38. 1 sign bit, 8 exponent bits, 23 mantissa bits. `Float32` is natively accelerated on all GPU and CPU architectures. Use for general numeric work and GPU computation. ### `Float64` {#float64} 64-bit IEEE 754 double-precision. Use when 7 significant decimal digits aren't enough: scientific simulations, financial calculations, or accumulated sums where rounding errors compound. 52 mantissa bits give roughly 15-16 significant decimal digits. 1 sign bit, 11 exponent bits, 52 mantissa bits. ### `BFloat16` {#bfloat16} 16-bit brain floating-point developed by Google Brain for deep learning. 1 sign bit, 8 exponent bits, 7 mantissa bits. Google Brain designed it to solve a specific problem with `Float16` in training: `Float16`'s 5 exponent bits create a dynamic range too narrow for neural networks. Gradients overflow and underflow. `BFloat16` matches `Float32`'s 8 exponent bits exactly, so values stay in range throughout forward and backward passes. The matching exponent range also makes `Float32`/`BFloat16` conversion cheap: just truncate or extend the mantissa, no remapping. This makes mixed-precision training feasible: compute in `BFloat16` for speed and memory savings, keep optimizer state in `Float32` for precision. That combination drove its wide adoption as a training format. Use it for ML training and inference on supported hardware. The 7-bit mantissa is too imprecise for scientific or financial work. `BFloat16` is not supported on all platforms. It's currently unavailable on Apple Silicon. Natively accelerated on NVIDIA Ampere (A100) and later, AMD MI300X and later, and Intel CPUs with AMX or AVX-512 BF16 (Sapphire Rapids and later). ### Low-precision types Fewer bits per value means more values per register, less memory bandwidth, and higher throughput on specialized hardware. You trade mantissa precision for the ability to fit larger models or larger batches on the same silicon. These formats follow the OCP Microscaling Formats (MX) and OFP8 specifications. There's no single `Float8` type in Mojo. It's a colloquial umbrella for the five 8-bit floating-point variants exposed as `Scalar` aliases: `Float8_e4m3fn`, `Float8_e4m3fnuz`, `Float8_e5m2`, `Float8_e5m2fnuz`, and `Float8_e8m0fnu`. Each has its own exponent/mantissa layout and set of supported operations. `.float8_e3m4` also exists as a dtype value but has no `Scalar` alias; use `Scalar[.float8_e3m4]` directly. `Float8` formats are used in machine learning workloads where memory bandwidth matters more than precision. These types require GPU hardware for efficient execution. `Float8` types can't convert to or from any integer type on any platform, including `Bool`. They only convert between floating-point types: `Float16`, `Float32`, `Float64`, `BFloat16`, and other supported `Float8` variants. ### Floating point naming conventions The suffixes encode special properties of each format: - **`fn`**: finite -- no infinity or negative infinity encodings - **`uz`**: unsigned zero -- no negative zero encoding - **`fnu`**: finite, no sign, unsigned zero The name encodes the layout: `e4m3` means 4 exponent bits and 3 mantissa bits. `fn` means no infinities, and `uz` means unsigned zero. For example, `Float4_e2m1fn` is a 4-bit format with 2 exponent bits and 1 mantissa bit, defined by the Open Compute MX specification. :::note Vendor naming `Float8_e4m3fn` is the same format across vendors, but named differently: Mojo, PyTorch, JAX, and LLVM call it `e4m3fn`, while OCP, NVIDIA CUDA, and AMD ROCm call it `e4m3`. ::: ### Hardware requirements Support varies significantly by type and operation. None of these types support arithmetic at runtime on CPU. **Arithmetic support** (tested on ARM CPU, NVPTX sm_90a, AMDGCN gfx942): | Type | Comptime | CPU | NVPTX | AMDGCN | |-------------------|----------|-----|-------|--------| | `Float8_e4m3fn` | ✅ | ❌ | ✅ | ❌ | | `Float8_e4m3fnuz` | ✅ | ❌ | ❌ | ❌ | | `Float8_e5m2` | ✅ | ❌ | ✅ | ❌ | | `Float8_e5m2fnuz` | ✅ | ❌ | ❌ | ❌ | | `.float8_e3m4` | ❌ | ❌ | ❌ | ❌ | NVPTX support for `Float8_e4m3fn` and `Float8_e5m2` is emulated by the compiler: operands are upconverted to a wider type, the operation runs in that wider type, and the result is downconverted back. There are no native fp8 arithmetic instructions. - `Float8_e3m4` has no arithmetic support at any stage, including comptime. Most of its conversions work only at comptime. - `Float4_e2m1fn` requires NVIDIA Blackwell (B200) or later. - `Float32` and `Float64` are the portable alternatives for CPU and cross-platform code. ### IEEE 754 special values IEEE 754 floating-point types support special values: | Value | Meaning | |--------|-------------------| | `inf` | Positive infinity | | `-inf` | Negative infinity | | `nan` | Not a number | | `-0.0` | Negative zero | Access these via `SIMD` constants: ```mojo var x = Float32.MAX # largest value var y = Float32.MIN # smallest value var z = Float32.MAX_FINITE # largest finite value var w = Float32.MIN_FINITE # smallest (most negative) finite value ``` `MAX` and `MIN` may be infinite for floating-point types. `MAX_FINITE` and `MIN_FINITE` give the largest and smallest representable finite values. Low-precision formats marked `fn` (finite) don't have infinity encodings. Formats marked `uz` (unsigned zero) don't have negative zero. ### Floating point precision Floating-point arithmetic introduces rounding errors. Two values that look equal after computation may differ by a tiny amount. Comparing with `==` can give unexpected results: ```mojo # Compile-time: exact result comptime exact = 3.0 * (4.0 / 3.0 - 1.0) # Force runtime: rounding error appears var three = 3.0 var finite = three * (4.0 / three - 1.0) print(exact, finite) # 1.0 0.99999999999999978 print(exact == finite) # False ``` For approximate comparisons, check whether the difference is within an acceptable tolerance with `std.math`'s `isclose()`. ## Numeric literals Mojo has two compile-time literal types: `IntLiteral` and `FloatLiteral`. They support arbitrary precision and exist only during compilation. ### IntLiteral When you write a bare integer like `42`, its type is `IntLiteral`. It doesn't become a concrete type until it's used in a context that requires one: ```mojo var a: Int = 42 # Becomes Int var b: Int8 = 42 # Becomes Int8 var c: Float32 = 42 # Becomes Float32 var d: UInt64 = 1_000_000 # Becomes UInt64 ``` `IntLiteral` is arbitrary-precision at compile time. It has no fixed bit width, so compile-time calculations won't overflow or lose precision. At runtime, `IntLiteral` values materialize to `Int`: ```mojo # Compile-time: arbitrary precision, no overflow comptime big = 2 ** 200 # Runtime: materializes to Int (word-sized) var x = 42 # IntLiteral 42 materializes to Int ``` `IntLiteral` supports all arithmetic and comparison operators at compile time. ### FloatLiteral When you write a decimal constant like `3.14`, its type is `FloatLiteral`. It doesn't become a concrete type until it's used in a context that requires one: ```mojo var x: Float32 = 3.14 # Becomes Float32 var y: Float64 = 3.14 # Becomes Float64 var z: BFloat16 = 0.5 # Becomes BFloat16 ``` `FloatLiteral` provides compile-time constants for special values: | Constant | Value | |----------------------------------|-------------------| | `FloatLiteral.nan` | Not a number | | `FloatLiteral.infinity` | Positive infinity | | `FloatLiteral.negative_infinity` | Negative infinity | | `FloatLiteral.negative_zero` | Negative zero | Use `is_nan()` and `is_neg_zero()` to test for these values, since `nan == nan` is `False` and `negative_zero == 0.0` is `True`. ### Literals in expressions Literals adapt to the types around them. When a literal appears next to a typed value, it takes on that value's type: ```mojo var x = Float32(1.0) var y = x * 0.5 # 0.5 becomes Float32 var z = x + 2 # 2 becomes Float32 ``` This isn't implicit conversion. The literal doesn't have a runtime type yet. It becomes whatever type the context requires. Variables have a fixed type and never convert implicitly. ## Explicit conversions Converting between numeric types always requires an explicit initializer or cast. Mojo doesn't perform implicit numeric conversions between variables: ```mojo var i = 42 # Int var f = Float32(i) # Int → Float32 var u = UInt64(i) # Int → UInt64 var narrow = Int8(i) # Int → Int8 ``` Between `SIMD`-based types, use `.cast[]`: ```mojo var a = Float32(3.14) var b = a.cast[.int32]() # Float32 → Int32 var c = a.cast[.float64]() # Float32 → Float64 ``` Between `Int` and `SIMD`-based types, use initializers: ```mojo var i = 42 # Int var s = Int64(i) # Int → Int64 var back = Int(s) # Int64 → Int ``` ### Why conversions are explicit Implicit numeric conversions can hide precision loss and sign changes. For example, `Int64(-1)` becoming `UInt64(18446744073709551615)` is a bug, not a convenience. Mojo requires an explicit conversion so the intent is clear. Literals are the exception. A literal like `42` can become `Float32(42.0)` because the compiler performs the conversion at compile time and can guarantee it is exact. Variables are different. A value like `x: Int = 300` becoming an `Int8` would silently lose data, so Mojo requires you to write the conversion explicitly. ## Sharp edges ### `Int` width is platform-dependent `Int` is 64-bit on most platforms today, but it's defined as machine width. Code that assumes 64-bit `Int` will break on 32-bit targets. Use `Int64` when you need a fixed width. ### Integer arithmetic wraps on overflow Integer arithmetic wraps on overflow using two's complement: - Signed overflow wraps into the negative range. Adding `1` to `Int8` value `127` produces `-128`. - Unsigned overflow wraps to zero. Adding `1` to `UInt8` value `255` produces `0`. Mojo doesn't trap on overflow. If you need overflow detection, check the operands before the operation. ```mojo var x = Int8(127) var y = x + Int8(1) # -128 (wraps) ``` ### Float-to-int truncates toward zero ```mojo var x = Int(Float32(3.9)) # 3, not 4 var y = Int(Float32(-3.9)) # -3, not -4 ``` ### NaN comparisons always return `False` This includes `NaN == NaN`. It affects SIMD masks and conditional selection: ```mojo var x = Float32.MAX * 2.0 # inf var nan = x - x # NaN print(nan == nan) # False ``` ### 128-bit and 256-bit integers are software-emulated `Int128`, `Int256`, `UInt128`, and `UInt256` exist but have limited hardware support on most platforms. Avoid them in performance-critical code without benchmarking. ### Float8 types require GPU hardware The `Float8` variants are designed for ML workloads on GPUs with native support. On CPUs, operations on these types may be emulated or unavailable. --- ## Mojo operator reference ## Precedence table Operator precedence and associativity. Higher-precedence operators bind tighter. Unless noted, operators associate left to right. ### From highest to lowest precedence | Precedence | Operators | Notes | |------------|-----------------------------|-----------------------------------| | 1 | `()` `[]` `.` | Call, subscript, attribute | | 2 | `**` | Exponentiation, right-associative | | 3 | `+x` `-x` `~x` | Unary prefix | | 4 | `*` `@` `/` `//` `%` | Multiplicative | | 5 | `+` `-` | Additive (addition, subtraction) | | 6 | `<<` `>>` | Bitwise shift (left, right) | | 7 | `&` | Bitwise AND | | 8 | `^` | Bitwise XOR (not transfers) | | 9 | `\|` | Bitwise OR | | 10 | `==` `!=` `<` `<=` `>` `>=` | Comparisons, chainable | | 10 | `in` `not in` | Membership, chainable | | 10 | `is` `is not` | Identity, chainable | | 11 | `not` | Boolean NOT, prefix | | 12 | `and` | Boolean AND, short-circuits | | 13 | `or` | Boolean OR, short-circuits | | 14 | `if`-`else` | Ternary, right-associative | | 15 | `:=` | Walrus operator | _Prefix operators_: positive (`+x`), negative (`-x`), bitwise NOT complement (`~x`) _Multiplicative operators_: times (`*`), matrix multiplication (`@`), divide (`/`, integer types round towards zero), flooring divide (`//`, integer types round towards negative infinity), modulo (`%`). _Comparison operators_: equality (`==`), inequality (`!=`), less-than (`<`), less-than-or-equal (`<=`), greater-than (`>`), greater-than-or-equal (`>=`) :::note Assignment operators (`=` `+=` `-=` `*=` `/=` `//=` `%=` `**=` `@=` `&=` `\|=` `^=` `<<=` `>>=`) are statements, not expressions. They are not part of expression precedence. ::: ## Right-associative operators Most operators are left-associative. For example, `a - b + c` groups as `(a - b) + c`. Two infix operators are right-associative: exponentiation (`a ** b`) and Mojo's ternary `if`-`else`. ### Exponentiation (`**`) ```mojo 2 ** 3 ** 4 ``` groups as ```mojo 2 ** (3 ** 4) ``` Equivalent to `pow(2, 81)`. In Mojo `pow(a, b)` and `a ** b` are interchangeable. ### Ternary (`if`-`else`) ```mojo "low" if value < 10 else "high" if value > 100 else "mid" ``` groups as ```mojo "low" if value < 10 else ("high" if value > 100 else "mid") ``` ## Chaining operations All comparison operators can be chained: ```mojo a < b < c # equivalent to: (a < b) and (b < c) a == b == c # equivalent to: (a == b) and (b == c) a < b == c # equivalent to: (a < b) and (b == c) a < b <= c != d # equivalent to: (a < b) and (b <= c) and (c != d) ``` Each intermediate value is evaluated once. - Chaining only applies between operators at the same precedence. `2 ** 3 == 8` isn't a chain. It evaluates as `(2 ** 3) == 8` since exponentiation binds tighter than comparison. - Comparison, membership, and identity operators share the same precedence and chain together. `5 != a < b in c` is valid and evaluates as `(5 != a) and (a < b) and (b in c)`. ## Implementing operators for custom types Mojo doesn't limit operators use to built-in types. Each operator has a set of dunder methods your custom types can implement. Once added, you can use operators in code instead of calling methods. ### Infix operator method types Each infix operator has up to three forms that decide which operand's method will run. For `a op b`: - _Forward_: Mojo tries the forward method first. `a + b` calls `a.__add__(b)`. - _Reversed_: If the forward method doesn't exist or can't handle `b`'s type, Mojo falls back to the reversed method on `b`. `a + b` calls `b.__radd__(a)`. - _In-place_: Called for compound assignment. `a += b` calls `a.__iadd__(b)`. For example, if `a` uses a `CustomVector` type: - `a + 5`: calls the forward method `a.__add__(5)` - `5 + a`: Int doesn't know custom types. Falls back to the reversed method, `a.__radd__(5)` - `a += 5`: calls the in-place `a.__iadd__(5)` method ### Arithmetic Implement the methods directly on your struct to use `instance OP instance`, `instance OP= instance`. | Operator | Forward | Reversed | In-place | |----------|----------------|-------------------|-------------------| | `+` | `__add__()` | `__radd__()` | `__iadd__()` | | `-` | `__sub__()` | `__rsub__()` | `__isub__()` | | `*` | `__mul__()` | `__rmul__()` | `__imul__()` | | `/` | `__truediv__` | `__rtruediv__()` | `__itruediv__()` | | `//` | `__floordiv__` | `__rfloordiv__()` | `__ifloordiv__()` | | `%` | `__mod__()` | `__rmod__()` | `__imod__()` | | `**` | `__pow__()` | `__rpow__()` | `__ipow__()` | | `@` | `__matmul__()` | `__rmatmul__()` | `__imatmul__()` | In-place operators are syntactic sugar for the operator applied to the variable with assignment: ```mojo x += y # x = x + y x -= y # x = x - y x *= y # x = x * y x /= y # x = x / y x //= y # x = x // y x %= y # x = x % y x **= y # x = x ** y x @= y # x = x @ y ``` **Traits:** [`Powable`](/docs/std/math/math/Powable/) requires `__pow__()`, doesn't provide a default. ### Bitwise Implement the methods directly on your struct to use `a OP b`, `a OP= b`. | Operator | Forward | Reversed | In-place | |----------|----------------|-----------------|-----------------| | `&` | `__and__()` | `__rand__()` | `__iand__()` | | `\|` | `__or__()` | `__ror__()` | `__ior__()` | | `^` | `__xor__()` | `__rxor__()` | `__ixor__()` | | `<<` | `__lshift__()` | `__rlshift__()` | `__ilshift__()` | | `>>` | `__rshift__()` | `__rrshift__()` | `__irshift__()` | Bitwise operators are typically implemented on integer and flag types. Like arithmetic operators, in-place bitwise operators are syntactic sugar for the operator applied to the variable with assignment: ```mojo x &= y # x = x & y x |= y # x = x | y x ^= y # x = x ^ y x <<= y # x = x << y x >>= y # x = x >> y ``` ### Unary operators Implement the methods directly on your struct to support prefix operators like `-x`, `+x`, and `~x`. | Operator | Method | |----------|----------------| | `-x` | `__neg__()` | | `+x` | `__pos__()` | | `~x` | `__invert__()` | Mojo offers one postfix unary operator. | Operator | Method | |----------|------------------------------| | `x^` | Compiler implementation only | Use the `^` sigil for ownership transfer. `consume(a^)` transfers ownership of `a` to the `consume` function. - If the `consume` argument uses the `var` convention, the transfer moves the value. - If not, the value is copied and the original is left intact. After transfer, the `a` variable is uninitialized. You can't re-use the name until it's assigned a new value. ```mojo consume(a^) # transfers ownership of `a`'s value, leaving `a` uninitialized ``` **Disambiguation:** `^` means XOR when followed by a value, and transfer when used directly after a variable name. ```mojo a ^ b # XOR a^ # transfer ``` ### Comparison operators Implement the methods directly on your struct to use `a OP b`. | Operator | Method | Trait | Default? | |----------|------------|--------------|----------| | `==` | `__eq__()` | `Equatable` | Yes | | `!=` | `__ne__()` | `Equatable` | Yes | | `<` | `__lt__()` | `Comparable` | No | | `<=` | `__le__()` | `Comparable` | Yes | | `>` | `__gt__()` | `Comparable` | Yes | | `>=` | `__ge__()` | `Comparable` | Yes | **Traits:** [`Equatable`](/docs/std/builtin/comparable/Equatable/) provides `__eq__()` if all your struct's fields conform to `Equatable` using pairwise field comparison. `__ne__()` derives from `__eq__()`. [`Comparable`](/docs/std/builtin/comparable/Comparable/) provides `__le__()`, `__gt__()`, and `__ge__()`, all derived from `__lt__()`. You implement `__lt__()`. `Comparable` refines `Equatable`, so conforming to `Comparable` requires both traits. If all your fields are `Equatable`, you implement `__lt__()` at a minimum. ### Identity and membership operators Implement the methods directly on your struct to use `a OP b`. | Operator | Method | Trait | Default? | |----------|------------------|----------------|----------| | `is` | `__is__()` | `Identifiable` | No | | `is not` | `__isnot__()` | `Identifiable` | Yes | | `in` | `__contains__()` | — | No | | `not in` | `__contains__()` | — | No | `x in collection` calls `collection.__contains__(x)`. The method is on the **container**, not the element. `not in` calls the same method and negates the result. `is` tests object identity, not equality. Stdlib types that implement it include `ArcPointer`, `PythonObject`, and `Optional` (for `is None` checks). **Traits:** [`Identifiable`](/docs/std/builtin/identifiable/Identifiable/) requires `__is__()`. `__isnot__()` is provided (calls `not (self is rhs)`). ### Subscript operators Implement the methods directly on your struct to use `a[key]` reads and `a[key] = b` assigns. | Operation | Method | |--------------------------|-----------------| | `obj[key]` (read) | `__getitem__()` | | `obj[key] = val` (write) | `__setitem__()` | Both accept variadic arguments (for multi-dimensional indexing). --- ## Mojo simple statements reference A *simple statement* performs a single action on one logical line. Multiple simple statements can share a line when separated by semicolons. ## Import statements *Import statements* expose modules and their members to the current scope. Imports can appear at module level, inside functions, or inside other scopes. They don't need to appear at the top of a file. ```mojo import std.math from std.collections import Dict, Set ``` Use parentheses to import on multiple lines for readability and support clean commit diffs: ```mojo from std.collections import ( Dict, Set, List, # Trailing comma is legal ) ``` ### Module imports ```mojo import std.math import numpy as np # Alias the module name to avoid collisions ``` ### Selective imports ```mojo from std.math import sqrt, pi from std.collections import Dict as Dictionary # Alias the imported name ``` ### Wildcard imports ```mojo from std.math import * # Imports all public names from the std.math module ``` ## Expression statements An *expression statement* evaluates an expression for its side effects. When the result is unused (other than `None`), the compiler warns: ```mojo x + y # Warning: result is unused ``` The compiler doesn't warn when the result is `None`, which is common for functions called for their side effects: ```mojo print("hello") # Side effect: prints trigger() # Side effect: called for behavior ``` Assign the result to `_` to explicitly discard it and silence the warning: ```mojo _ = update() # Explicitly discard the result ``` Expressions are not valid at module scope or in struct bodies outside of methods. ## Assignment statements *Assignment statements* bind values to names with `=`. Use `var` declarations to declare new variables: ```mojo var x = 42 var name = "Alice" var result = compute() ``` Use `ref` declarations to create reference bindings: ```mojo ref y = my_list[3] # `y` is a reference to the value at `my_list[3]` ``` The `y` reference binding does not create a new value. It creates a reference to the existing value at `my_list[3]`. Modifying `y` modifies the value in `my_list[3]`; modifying `my_list[3]` modifies what `y` reads. Annotated assignments bind a type to a name, with an optional initializer. Type annotations aren't required, but they improve readability and catch errors: ```mojo var x: Int = 42 var name: String = "Alice" var values: List[Float64] = [] ``` A `var` without a type *and* without an initializer is an error: ```mojo var x # Error: declaration must have either a type or an initializer var x: Int # OK: type provided, value uninitialized var x = 42 # OK: type inferred from initializer ``` When types are complex and long to write, you can use comptime aliases to keep your code concise: ```mojo comptime Vec3 = List[Float64] var position: Vec3 = [0.0, 0.0, 0.0] ``` ### Multiple assignment Multiple assignments give you a concise syntax for initializing variables. Assign the same value to multiple names. This is right-associative. `z` is assigned first, then `y`, then `x`. If the RHS has side effects, they run once: ```mojo # Good for initializing counters/flags to the same literal. var x = var y = var z = "Hello" print(x, y, z) # Hello Hello Hello # Mixing conventions ref a = var b = var c = "Hello" print(a, b, c) a = "World" print(b) # World ``` Destructuring assignment: ```mojo var a, b = 1, 2 # Destructuring assignment var (c, d) = (1, 2) # Equivalent destructuring # not "assign tuple to tuple" print(a, b, c, d) # 1 2 1 2 def returns_pair() -> Tuple[Int, Int]: return (1, 2) var e, f = returns_pair() print(e, f) # 1 2 ``` Avoid multiple assignments for destructuring unrelated values. ```mojo var temperature, name = 98.6, "Bob" ``` reads worse than two lines: ```mojo var temperature = 98.6 var name = "Bob" ``` ### Simple swaps ```mojo var a, b, c = 1, 2, 3 a, b, c = c, a, b print(a, b, c) # 3 1 2 ``` ### Augmented assignment Augmented assignment is syntactic sugar that combines an operation with assignment. The left-hand side is evaluated once: ```mojo x += 5 # x = x + 5 x -= 2 # x = x - 2 x *= 3 # x = x * 3 x /= 4 # x = x / 4 x //= 2 # x = x // 2 x %= 7 # x = x % 7 x **= 2 # x = x ** 2 x @= m # x = x @ m (matrix multiply) x &= mask # x = x & mask x |= flags # x = x | flags x ^= bits # x = x ^ bits x <<= 1 # x = x << 1 x >>= 1 # x = x >> 1 ``` ## The pass statement `pass` is a no-op. Use it as a placeholder where a statement is required but no action is needed: ```mojo def not_ready(): pass struct Empty: pass ``` `pass` is required in empty function and struct bodies to avoid syntax errors. ## The return statement `return` exits a function and optionally returns a value: ```mojo def greet(name: String): if not name: # String is falsy when empty return print(t"Hello, {name}!") def get_value() -> Int: return 42 def early_exit(items: List[Int], target: Int) -> Bool: for item in items: if item == target: return True return False ``` A function without an explicit `return` implicitly returns `None`. `return` is only valid inside a function: ```mojo return 42 # Error: cannot return from this context ``` ## The raise statement `raise` raises an error. The function must be declared with `raises` or included within a `try` block: ```mojo def validate(value: Int) raises -> Bool: if value < 0: raise Error("value must be non-negative") return True def mitigate_risk(): try: if not perform_some_test(): raise Error("test failed") # perform risky work, knowing test passed except e: log(e) ``` To propagate errors from a `try` block, use a bare `raise` to re-raise the current error: ```mojo try: validate(value) # perform work, knowing value is valid except e: raise # Re-raises current error to the next handler ``` Raising outside a valid context is an error: ```mojo raise Error("oops") # Error: cannot raise error in this context # (surround with try, or mark function as raises) raise # Error: no contextual error to reraise # (bare raise requires an active except block) ``` ## Control flow with break and continue statements `break` exits the innermost loop immediately. `continue` skips to the next iteration: ```mojo for x in range(10): if x == 5: break # Stop at 5 if x % 2 == 0: continue # Skip even numbers print(x) # Prints 1, 3 ``` ## Compile-time declarations `comptime` declares a compile-time constant. The value must be computable at compile time: ```mojo comptime SIZE = 256 comptime MAX = SIZE * 2 ``` `comptime` declares associated types in traits, and can be used to create type aliases and trait composition aliases: ```mojo comptime Permissive = ImplicitlyCopyable & Deinitable trait SimpleTrait(Writable): # `SimpleTrait` refines `Writable` comptime Element = Permissive # Associated type with a comptime alias ``` A trailing `where` clause constrains a parametric declaration's parameters. The compiler checks the condition at each use: ```mojo comptime AscendingMidpoint[lo: Int, hi: Int]: Int where lo < hi = (lo + hi) / 2 def main(): comptime mid = AscendingMidpoint[2, 10] # 6 # comptime bad = AscendingMidpoint[10, 2] # Error: lo < hi not satisfied ``` A `where` clause requires conditions the compiler can evaluate at compile time, such as comparisons, boolean combinations, and `conforms_to()`. --- ## Mojo struct declarations reference A struct defines a custom type with fields and methods. Structs are value types: each variable holds its own independent copy rather than a reference to shared data. ```text struct Name: body struct Name[parameter-list]: body struct Name(TraitA, TraitB): body struct Name[parameter-list](TraitA, TraitB): body ``` By convention, struct names use `PascalCase`. `Self` (capital S) refers to the struct's own type inside the body. `self` (lowercase) is a conventional argument name for the instance. ```mojo from std.math import sqrt struct Point: var x: Int var y: Int def __init__(out self, x: Int, y: Int): self.x = x self.y = y def distance(self) -> Float64: return sqrt( Float64(self.x * self.x + self.y * self.y) ) def main(): var p = Point(3, 4) print(p.distance()) # 5.0 ``` ## Struct body elements A struct body can contain these elements: | Element | Syntax | Role | |-----------------------|-------------------------------|----------------------------| | Field | `var name: Type` | Instance data | | Method | `def name(self, ...)` | Instance behavior | | Static method | `@staticmethod def name(...)` | Type-level behavior | | Compile-time constant | `comptime name = value` | Evaluated at compile time | | Initializer | `def __init__(out self, ...)` | Constructs an instance | | Deinitializer | `def __deinit__(deinit self)` | Cleanup at end of lifetime | The most minimal struct uses `pass` for an empty body: ```mojo struct ValidationError: pass ``` Structs can't be nested inside other structs, traits, or functions: ```mojo struct Outer: struct Inner: # Error: nested struct not supported here pass ``` ## Fields Declare each field with `var` and a type annotation. Fields can't have default values. All fields must be initialized in `__init__()`: ```mojo struct Color: var r: UInt8 var g: UInt8 var b: UInt8 def __init__(out self, r: UInt8, g: UInt8, b: UInt8): (self.r, self.g, self.b) = (r, g, b) ``` Every field requires a type annotation: ```mojo struct Unsound: var x # Error: struct field declaration must have a type ``` Field types must be concrete, not traits. A struct parameter establishes a concrete type at compile time: ```mojo struct Unsound: var item: Writable # Error because dynamic traits not supported @fieldwise_init struct Sound[T: Writable & Copyable & Deinitable]: var item: Self.T # OK: concrete at compile time def main(): var g = Sound[Int](item=42) print(g.item) # 42 ``` ### Synthesized initializers The `@fieldwise_init` decorator synthesizes an `__init__()` from the struct's fields: ```mojo @fieldwise_init struct Color: var r: UInt8 var g: UInt8 var b: UInt8 def main(): var color = Color(255, 0, 0) print(color.r, color.g, color.b) # 255, 0, 0 ``` Synthesis fails if any field is non-copyable and non-movable: ```mojo @fieldwise_init struct Alpha: var a: UInt8 @fieldwise_init struct Color: var r: UInt8 var g: UInt8 var b: UInt8 var alpha: Alpha # Error: cannot synthesize fieldwise init because field # 'alpha' has non-copyable and non-movable type 'Alpha' ``` ### Recursive references Structs can't point to themselves. Mojo won't let you build a type that stores another instance of itself, even when nested within an Optional: ```mojo struct Node: var value: String var next: Optional[Node] # Error about this being a recursive # reference ``` To build recursive data structures such as linked lists and trees, you must use unsafe pointers. ## Parameters Structs accept compile-time parameters in square brackets. Parameters are accessed through `Self` inside the struct body. `Self.T` refers to the parameter `T`. Bare `T` isn't valid in the struct body: ```mojo @fieldwise_init struct Pair[T: Copyable & Deinitable]: var first: Self.T var second: Self.T ``` ## Trait conformance Declare conformance in parentheses after the name or parameter list. Separate multiple traits with commas or compose them with `&`: ```mojo @fieldwise_init struct MyInt(Writable, Copyable): var value: Int def write_to[W: Writer](self, mut writer: W): writer.write(self.value) def main(): var my_int = MyInt(42) print(my_int) # 42 ``` Conformance commits the struct to implementing every method and associated type the trait requires. Missing items produce errors: ```mojo @fieldwise_init struct Incomplete(Sized): var value: Int # Error: 'Incomplete' does not implement all requirements # for 'Sized' # Note: required function '__len__' is not implemented ``` ### Conformance lists A conformance list accepts traits and conditional `where` clauses: ```mojo @fieldwise_init struct Pair[T: Copyable & Deinitable]( Equatable where conforms_to(T, Equatable) ): var first: Self.T var second: Self.T ``` ### Implicit conformances The compiler automatically conforms every struct to `AnyType` and `Deinitable` when all members are also `Deinitable`. With parameterized types, the parameter's traits must include `Deinitable` for this to apply: ```mojo @fieldwise_init struct Box[T: Copyable & Deinitable]( Equatable where conforms_to(T, Equatable) ): var item: Self.T def main(): var box = Box(42) print(box.item) # OK ``` Without `Deinitable`, the compiler can't verify that the struct is safe to destroy using the built-in `__deinit__()` deinitializer: ```mojo @fieldwise_init struct Box[T: Copyable]( Equatable where conforms_to(T, Equatable) ): var item: Self.T def main(): var box = Box(42) print(box.item) # Error about the 'box' being abandoned without being destroyed. ``` ### Synthesized lifecycle methods If a struct conforms to `Movable` but doesn't define `__init__(move:)`, the compiler synthesizes one that moves each field. The same applies to `Copyable` and `__init__(copy:)`. Synthesis fails if any field can't support the operation: ```mojo struct Unsound(Copyable): var item: SomeMoveOnlyType # Error about synthesizing the copy initializer because # field 'item' has non-copyable type SomeMoveOnlyType ``` ### Default method conflicts When two traits provide conflicting defaults for the same method, the struct must implement it manually: ```mojo trait A: def foo(self) -> Int: return 42 trait B: def foo(self) -> Int: return 1024 @fieldwise_init struct S(A & B): pass # Error about conflicting default implementations in two traits # reminding you to implement the implementation manually ``` ### Conditional conformance Conditional conformance lets a struct conform to a trait when certain conditions are met. For example, the following structs conform to a set of (mostly hypothetical) traits when their parameters meet specific criteria: ```mojo from std.sys import is_gpu @fieldwise_init struct Mathematical( GPUComputable where is_gpu() ): # conforms only on GPU targets @fieldwise_init struct FixedBuffer[T: Copyable, N: Int]( Iterable where N > 0 ): # conforms if N is one or more, but not if N is zero or negative @fieldwise_init struct Tensor[dtype: DType]( FloatMath where dtype.is_floating_point() ): # conforms when dtype is a floating point type @fieldwise_init struct Tagged[kind: StringLiteral]( Printable where kind == "debug" ): # only conforms in debug mode @fieldwise_init struct Box[T: Copyable]( Equatable where conforms_to(T, Equatable) ): # conforms to Equatable only when T does ``` ### Conditional conformance and compile-time values Conditional conformance can depend only on information known at compile time. While it often uses traits to constrain conformance, conditional conformance isn't limited to traits. A condition can use any compile-time value that can be evaluated in a clear and consistent way. For example, a type might conform to a trait only on a specific platform (such as NVIDIA GPUs or Apple Silicon with Metal) or when a compile-time constant has a given value. If the condition can be fully resolved at compile time, it can restrict conformance. That said, conformance can't depend on a computed compile-time member. Trait conformance is part of the type's signature, and the signature is needed to resolve members. Depending on a computed member would create a circular dependency. ### Conditional conformance and default implementations Conditional conformance and default implementations are independent features, but they often work together. The `Writable` trait offers a default implementation that uses reflection to automatically write struct fields. Declare the trait conformance after ensuring that all fields are `Writable`: ```mojo @fieldwise_init struct Point(Writable): var x: Float64 var y: Float64 ``` Consider a parameterized version of this `Pair` type: ```mojo @fieldwise_init struct Pair[T: Copyable & Deinitable]: var first: Self.T var second: Self.T ``` If `T` isn't `Writable`, the struct can still declare `Writable` conformance by providing its own implementation of `Writable`'s required methods. This is impractical without an API surface that describes `T` instances. A better solution is to use conditional conformance. Ensure `Pair` conforms to `Writable` only when its fields do. Test the `T` type with `conforms_to()` in a `where` clause in the struct's conformance list: ```mojo @fieldwise_init struct Pair[T: Copyable & Deinitable]( Writable where conforms_to(T, Writable) ): var first: Self.T var second: Self.T ``` You can print `Pair[Int]` because `Int` is `Writable`, but not `Pair[NotWritable]`, which doesn't conform to `Writable`: ```mojo @fieldwise_init struct NotWritable(ImplicitlyCopyable & Deinitable): var item: Int def main(): var not_writable = NotWritable(42) var pair = Pair(not_writable, not_writable) print(pair) # Error regarding 'Writable' nonconformance ``` ### Mixed trait lists You can combine conditional and non-conditional traits in the same conformance list: ```mojo @fieldwise_init struct Pair[T: Copyable & Deinitable]( Equatable where conforms_to(T, Equatable), Writable where conforms_to(T, Writable), Copyable ): var first: Self.T var second: Self.T def main(): var pair1 = Pair(first=1, second=2) var pair2 = Pair(first=1, second=2) var pair3 = Pair(first=3, second=4) print(pair1 == pair2) # True print(pair1 == pair3) # False var pair4 = pair1.copy() # OK: Copyable conformance doesn't depend on T _ = pair4 ``` ## Methods Instance methods take `self` as the first argument. The convention on `self` determines access: - Bare `self`: immutable reference. - `mut self`: allows modification. - `out`, `deinit`: used by lifecycle methods. - `out`: also used to specify a named result slot. - `ref`: used to declare an argument with parametric mutability, and which must be passed in memory regardless of its type. A method without `self` is an error unless it's marked `@staticmethod`: ```mojo struct Unsound: def broken(): pass # Error: self argument must be present in instance method struct OK: @staticmethod def utility(): # No self required pass ``` `@staticmethod` marks a method that belongs to the type, not to instances. It can access type parameters and comptime members, can call other static methods, has no `self`, and has no access to instance fields and instance methods. Use static methods for utility functions related to the struct's mission that don't need an instance to work, such as factory methods and general-purpose helpers. ### Dunder methods *Dunder methods* (double-underscored names) let a struct work with operators, built-in functions, and lifecycle events: `__init__()`, `__add__()`, `__str__()`, and so on. ## Instance creation with initializers `__init__()` uses the `out self` convention to produce the newly initialized value. Every field must be assigned before `__init__()` returns: ```mojo struct Point: var x: Float64 var y: Float64 def __init__(out self, x: Float64, y: Float64): (self.x, self.y) = (x, y) def main(): var p = Point(3.0, 4.0) print(p.x, p.y) # 3.0 4.0 ``` Omitting `out self` is an error: ```mojo struct Unsound: var value: Int def __init__(self): pass # Error: __init__ method must return Self type with # 'out' argument ``` `__init__()` is implicitly static. Structs can define multiple `__init__()` overloads. ## Instance tear-down with deinitializers `__deinit__()` runs when the compiler detects no further access to an instance. Its `self` uses the `deinit` convention: ```mojo def __deinit__(deinit self): print("cleaning up") ``` Implicitly deinitable structs get a default `__deinit__()`. A custom `__deinit__()` overrides the default. `__deinit__()` can't be overloaded. :::note Explicitly destroyed structs write their own cleanup logic, using a consuming method with `deinit self`. Opt out with `Deinitable where False`. ::: ## `comptime` members `comptime` declares type-level members inside a struct. They're evaluated at compile time and can't be modified at runtime. Use them for constants, type aliases, and computed members: ```mojo @fieldwise_init struct Matrix2D[dtype: DType, w: Int, h: Int]: pass struct Test[dtype: DType]: comptime default_size = 1024 comptime DefaultMatrixType = Matrix2D[Self.dtype, Self.default_size, Self.default_size] comptime SquareMatrixType[size: Int] = Matrix2D[Self.dtype, size, size] def main(): print(Test[.int32].default_size) # 1024 ``` Access these constants on the instance or the type. For example, `Test[.int32]().default_size` and `Test[.int32].default_size`. --- ## Mojo trait declarations A *trait* defines requirements that a conforming type must satisfy, including methods, associated types, and constants. Traits are similar to *protocols* in Swift, *interfaces* in Java, and *traits* in Rust. When a type conforms to a trait, the compiler checks every requirement and rejects the code if anything is missing. ## Trait declarations Traits are declared with the `trait` keyword followed by the trait name and an optional refinement list. The body contains the trait's requirements, both required and provided: ```text trait Name: body trait Name(ParentA, ParentB): body ``` Traits must be declared at the top level of a file. They can't be nested inside structs, other traits, or functions: ```mojo struct Outer: trait Inner: # Error: nested trait not supported here ... ``` ### Marker traits Empty traits are called *marker traits* and signal that a type has a specific property or capability without refining other traits or declaring requirements: ```mojo trait AnyType: pass ``` Mojo marker traits include: `AnyType`, `TrivialRegisterPassable`, `RegisterPassable`, and `ImplicitlyCopyable`. They tell the compiler about a type's properties, supporting compile-time optimizations. ## Trait body elements A trait body can contain these elements: | Element | Syntax | Role | |-----------------------------------|-------------------------|------------------------------------------| | Required method | `...` body | Conforming types must implement | | Provided method | Code body | Inherited unless overridden | | Comptime member - associated type | `comptime Name: Trait` | Conforming types provide a concrete type | | Comptime member - required value | `comptime name: Type` | Conforming types provide a value | | Comptime member - constant | `comptime name = value` | Shared across all conforming types | ## Trait and member names Trait names must be valid identifiers. By convention, they describe a capability: `Writable`, `Hashable`, `Copyable`. ### Naming conventions | Element | Naming | Notes | |--------------------|--------------------------------------------------------------------|--------------------------------------------------------------------------| | Trait name | `PascalCase` | Capabilities gained by conformance: `Equatable`, `Copyable`, `PathLike`. | | Instance method | `lower_snake_case()` | Method's action: `write_to()`, `update()` | | Static method | `lower_snake_case()` | Method's action: `get_type_name`, `get_element_bitwidth` | | `comptime` members | Trait compositions are `PascalCase`. Values are `lower_snake_case` | Describes use: `KeyElement`, `element_bitwidth` | | Associated type | `PascalCase` | Describes use: `Element`, `Iterator` | :::caution When defining traits, avoid private member names with single or double underscore prefixes. It may cause required members to be hidden in generated documentation from trait users. For similar reasons, don't use `@doc_hidden` to hide trait members. The standard library has a small number of exceptions for required trait elements known to the compiler. ::: ## Methods Traits define both required and provided methods, and both instance and static members. Instance methods take `self` as the first parameter. Static methods require the `@staticmethod` decorator. ### Required methods An ellipsis (`...`) marks a required method. Conforming types must provide an implementation: ```mojo trait RequiredMethods: def required_method(self): ... @staticmethod def required_static_method(): ... @fieldwise_init struct SampleStruct(RequiredMethods): def required_method(self): print("Required method") @staticmethod def required_static_method(): print("Required static method") def main(): var s = SampleStruct() s.required_method() # Required method SampleStruct.required_static_method() # Required static method ``` ### Provided methods A method with a body other than `...` provides a default implementation. Conforming types automatically receive that behavior but can also override it: ```mojo trait ProvidedMethods: def provided_method(self): print("Provided method") @staticmethod def provided_static_method(): print("Provided static method") @fieldwise_init struct SampleStruct(ProvidedMethods): def provided_method(self): print("Overridden provided method") def main(): var sample = SampleStruct() sample.provided_method() # Overridden provided method SampleStruct.provided_static_method() # Provided static method ``` Both required and provided methods can return values: ```mojo trait Describable: def provided_describe(self) -> String: return "no description" # Type can override this implementation def required_describe(self) -> String: ... # Type must provide an implementation for this method ``` Provided behavior can't use implementation details from any specific conforming type, as a trait has no knowledge of a type's capabilities beyond those declared in the trait and refinement list. The behavior must work across all conforming types. ### `pass` vs `...` `pass` and `...` mean distinct things in trait bodies: - `...` marks a required method stub. - `pass` is a no-op that counts as a provided implementation body. It's only valid when the method returns `None`. If a method declares a return type but uses `pass` as its body, the compiler will encourage you to replace it with `...`: ```mojo trait Unsupported: def __compute__(self) -> Int: pass # Error because trait method with a return type must not use 'pass'. # Use '...' to declare the method as required. ``` ## Comptime members: associated types An associated type is a `comptime` member that declares a related subordinate type that conforming types must specify. For example, an associated type might be the `Element` type for a container or collection trait, or the `Key` and `Value` types for a map. The associated type is declared as a `comptime` member without an initializer. Only traits can use this declaration form. Conforming structs provide the concrete value that satisfies any constraints declared in the trait. In the following example, `Self` refers to the conforming type, so `Self.Associated` refers to the conforming type's value for the associated type `Associated`: ```mojo trait Boxable: comptime Associated: Writable & Copyable & Deinitable def unbox(self) -> Self.Associated: ... @fieldwise_init struct ConcreteBox(Boxable): comptime Associated = String var value: Self.Associated def unbox(self) -> Self.Associated: return self.value.copy() def main(): var box = ConcreteBox(value="Hello") var unboxed = box.unbox() # Known to be Copyable print(unboxed) # Known to be Writable _ = unboxed^ # Known to be Deinitable ``` Associated types can be assigned from call sites. This lets a trait work across type families: ```mojo comptime Base = Copyable & Deinitable & Writable @fieldwise_init struct Box[T: Base](Boxable): comptime Associated = Self.T var value: Self.Associated def unbox(self) -> Self.Associated: return self.value.copy() ``` A `comptime` without an assignment, type, or traits is an error: ```mojo trait Unsupported: comptime X # Error: expected '=' after comptime declaration trait Supported: comptime X: Copyable # OK: associated type comptime y: Int # OK: required value comptime z = 42 # OK: constant ``` Outside of traits, a `comptime` member without an initializer is an error: ```mojo struct Unsupported: comptime X: Int comptime Y: Copyable # Error: only traits may contain a comptime member # without an initializer ``` ## Comptime members: constants and required values A trait can declare `comptime` constants (shared value) and required assignments (conforming types must provide a value). ### Constants This trait provides a usable bitwidth for conforming types based on the `Element` associated type and a named trait composition: ```mojo from std.sys.info import bit_width_of comptime BaseElement = Copyable & Deinitable & Writable trait Test: comptime Element: RegisterPassable comptime element_bitwidth = bit_width_of[Self.Element]() @fieldwise_init struct SampleStruct[T: BaseElement](Test): comptime Element = Int64 var x: Self.T def show_element_bitwidth(self): print(Self.element_bitwidth) def main(): var s = SampleStruct[Int64](x=42) s.show_element_bitwidth() # 64 print(s.x) # 42 ``` :::note Don't use traits to define general-purpose constants like `PI` or `SPEED_OF_LIGHT`. Define them at the top level of a module for local use, or as a public member of a related struct for broader use. ::: ### Comptime members: required values A required value is a `comptime` member without an initializer. Conforming types must provide a value for it. This is useful when the trait needs a compile-time constant that varies across conforming types. For example, a `Measurable` trait might require a `unit` string and an `always_positive` boolean to validate measurements. In this example, the trait requires conforming types to provide a `unit` string, an `always_positive` boolean, and a `get_value()` method. The `validate()` function uses those requirements to check that the value is positive when `always_positive` is `True`: ```mojo trait Measurable: comptime unit: StaticString # Required comptime always_positive: Bool # Required def get_value(self) -> Float64: ... # Required method def validate[T: Measurable](measurement: T) raises: comptime if T.always_positive: if Float64(measurement.get_value()) < 0.0: raise Error(t"{T.unit} cannot be negative") @fieldwise_init struct Pascals(Measurable): comptime unit: StaticString = "Pa" comptime always_positive: Bool = True var value: Float64 def get_value(self) -> Float64: return self.value def main() raises: validate(Pascals(value=101325.0)) # Validation succeeds # validate(Pascals(value=-101325.0)) # Validation fails # run-time error: # "Unhandled exception caught during execution: Pa cannot be negative" ``` A similar conformance for `DegreesCentigrade` would set the `unit` to `"°C"` and `always_positive` to `False`. A value of `-10.0` would pass validation for `DegreesCentigrade` and fail for `Pascals`. ## Trait refinement When a trait refines another, its constraint includes the parent's constraint: ```mojo trait Printable: def to_string(self) -> String: ... trait PrettyPrintable(Printable): def to_pretty_string(self) -> String: ... @fieldwise_init struct Box[T: Copyable & Writable & Deinitable](PrettyPrintable): var value: Self.T def to_string(self) -> String: return String(t"Box({self.value})") def to_pretty_string(self) -> String: return String(t"Box with value: {self.value}") def render[T: PrettyPrintable](item: T): # to_string() available: PrettyPrintable refines Printable print(item.to_string(), "-", item.to_pretty_string()) def main(): render(Box(1)) # Box(1) - Box with value: 1 render(Box("hello")) # Box(hello) - Box with value: hello ``` A function requiring `T: PrettyPrintable` can call any method from `Printable` without naming it in the constraint. ### Constraint resolution order When the compiler resolves a trait constraint: - The parameter's declared traits are checked first. - Parent traits are included transitively. - If the constraint uses `&`, all composed traits must be satisfied. - If a `where` clause is present, its constraints are checked after parameter-level constraints. If any check fails, the compiler reports which trait the type doesn't conform to. A child trait can override a parent method by declaring a method with the same signature. The parent's version is replaced in the child's requirements. :::note Every trait implicitly refines `AnyType`. ::: ## `Some[]` as constraint sugar `Some[Trait(s)]` is shorthand for a type parameter constrained to that trait or composition. It's syntax sugar, not a new mechanism. It moves the constraint onto the argument instead of declaring a parameter in one place and using it in another. The compiler infers the concrete type at the call site, just as it does for an explicit parameter. If inference fails, the compiler asks for an explicit parameter instead. | Context | With a parameter | With `Some` | |---------------------|--------------------------------------------------|-----------------------------------------------| | Argument | `def foo[T: Intable, //](x: T)` | `def foo(x: Some[Intable])` | | Function type | `def f[F: def(Int) -> None](func: F)` | `def f(func: Some[def(Int) -> None])` | | Variadic | `def show[*Ts: Writable](*pack: *Ts)` | `def show(*pack: *SomeTypeList[Writable])` | | Operator overload | `def __getitem__[I: Indexer, //](self, idx: I)` | `def __getitem__(self, idx: Some[Indexer])` | ### Where `Some` doesn't work The compiler can't infer a concrete type for a struct field: ```mojo @fieldwise_init struct Struct(Writable): var x: Some[Copyable & Deinitable & Writable] # Error: a `Some` struct field has no concrete type to infer ``` Use an explicit type parameter instead: ```mojo @fieldwise_init struct Struct[T: Copyable & Deinitable & Writable](Writable): var x: Self.T ``` ## Trait restrictions Traits don't support parameter lists: ```mojo trait Unsupported[T]: # Error: trait declarations do not support ... # parameters ``` Traits can't declare or use fields: ```mojo trait Unsupported: var x: Int # Error: traits do not support 'var' fields ``` Traits don't support `where` clauses on methods: ```mojo trait Unsupported: def maybe(self) -> Int where conforms_to(Self, Sized): ... # Error: 'where' clauses on trait methods are not supported ``` ## Conformance checks The compiler checks every requirement and errors on unmet ones. ### Missing methods ```mojo trait Sized: def __len__(self) -> Int: ... @fieldwise_init struct SizedStruct[T: Copyable](Sized): var backing_store: List[Self.T] # Error about missing a trait's required function '__len__'. ``` ### Missing required members ```mojo trait Container: comptime Element: Copyable @fieldwise_init struct Bag(Container): var data: Int # Error: 'Bag' does not implement all requirements for # 'Container' # Note: required member 'Element' is not specified ``` ### Type mismatch on associated types ```mojo trait Taggable: comptime Tag: Sized @fieldwise_init struct Widget(Taggable): comptime Tag = Bool # Error since Bool is not Sized ``` ### Provided method conflicts When two traits in a struct's conformance list produce conflicting provided methods for the same method, the struct must implement it manually: ```mojo trait Greeter: def greet(self): print("Hello from Greeter") trait Welcomer: def greet(self): print("Welcome from Welcomer") @fieldwise_init struct Host(Greeter, Welcomer): pass # Error about a trait method requirement greet having conflicting # default implementations in Greeter and Welcomer; you must # implement it manually ``` --- ## Mojo types reference Mojo is statically typed. Every value has a type that is known at compile time. This page catalogs built-in types that are available in every program without an import. ## Built-in types and the prelude Built-in types come from the standard library *prelude*, which the compiler imports into every program. The prelude consists of the `builtin` package (`Int`, `Bool`, `Error`, and core traits) plus selected types from `collections`, `memory`, and `math`. Although they feel like part of the language itself, built-in types are ordinary structs defined in the standard library, just like a type you might write yourself. The prelude and compiler syntax are separate concerns. Some language constructs have *syntax* without a corresponding name in scope. For example, a set *display* doesn't require an import but the `Set` type does: ```mojo var primes = {2, 3, 5, 7} # set display: compiler syntax, no import from std.collections import Set var empty = Set[Int]() # the name Set must be imported ``` ## Numeric types Mojo's numeric types are built on `SIMD`, a fixed-size, homogeneous vector of primitive values. `Int`, the sized integer types (`Int8` through `Int256` and `UInt8` through `UInt256`), and the floating-point types (`Float16` through `Float64`, `BFloat16`, and the `Float8_*` formats) are all available through the prelude. For details on sizes, precision, overflow behavior, and conversions, see [Numeric types](/docs/reference/numeric-types/). ## String types All string types hold UTF-8 encoded text. Their bytes are guaranteed to be valid UTF-8. Construction enforces this. `String(from_utf8_lossy=...)` replaces invalid bytes, and `String(unsafe_from_utf8=...)` requires the caller to guarantee validity. | Type | What it is | |--------------------------------------------------------------------------|----------------------------------------------| | [`String`](/docs/std/collections/string/string/String/) | Owned, mutable, heap-allocated UTF-8 string. | | [`StringSpan`](/docs/std/collections/string/string_span/StringSpan/) | Non-owning view into existing UTF-8 data. | | [`StaticString`](/docs/std/collections/string/string_span/#staticstring) | A `StringSpan` over static, read-only data. | | [`StringLiteral`](/docs/std/builtin/string_literal/StringLiteral/) | Compile-time string constant from source. | | [`Codepoint`](/docs/std/collections/string/codepoint/Codepoint/) | A single Unicode codepoint. | A string literal in source is a `StringLiteral`. It materializes to a `String` at runtime, or to a `StringSpan` when the context requires a view. `StringSlice` remains available as a compatibility alias for `StringSpan`. For details, see [String literals](/docs/reference/literals/#string-literals). Length has three measurements, and they disagree for non-ASCII text: ```mojo var wave = String("👋🏽") # waving hand + skin-tone modifier print(wave.byte_length()) # 8 print(wave.count_codepoints()) # 2 print(wave.count_graphemes()) # 1 ``` `byte_length()` counts UTF-8 bytes, `count_codepoints()` counts Unicode codepoints, and `count_graphemes()` counts user-perceived characters. Pick the count that matches the question being asked. ## Collection types Mojo includes a flexible set of collection types. | Type | What it is | |------------------------------------------------------------------|-------------------------------------------------------------| | [`List`](/docs/std/collections/list/List/) | Dynamically sized, growable sequence. | | [`Dict`](/docs/std/collections/dict/Dict/) | Key-value mapping. | | [`Set`](/docs/std/collections/set/Set/) | Unordered collection of unique values. Requires an import. | | [`Optional`](/docs/std/collections/optional/Optional/) | A value that may or may not be present. | | [`Tuple`](/docs/std/builtin/tuple/Tuple/) | Fixed-size, heterogeneous group of values. | | [`Array`](/docs/std/collections/array/Array/) | Fixed-size array stored inline, with no heap allocation. | | [`Variant`](/docs/std/utils/variant/Variant/) | Holds one value from a fixed set of types. Requires import. | `List`, `Dict`, `Set`, and `Tuple` support display syntax, as described in [Expressions](/docs/reference/expressions/#collection-displays). `Set` and `Variant` aren't in the prelude. Import `Set` from `std.collections` and `Variant` from `std.utils`. ### Optional An `Optional[T]` holds a `T` or nothing. It's truthy when a value is present: ```mojo var maybe: Optional[Int] = 5 if maybe: print(maybe.value()) # 5 ``` `value()` aborts on empty `Optional`s. Guard it with a truthiness check, or call `or_else()` to supply a default: ```mojo var empty: Optional[Int] = None # empty.value() # aborts: the Optional is empty print(empty.or_else(0)) # 0 ``` ### Variant A `Variant` holds one value from a fixed set of types, tracked at runtime. Test the active type with `isa[T]()`, and read it by indexing with that type: ```mojo from std.utils import Variant var v = Variant[Int, String](5) print(v[Int]) # 5 v.set[String]("text") print(v[String]) # text ``` ## Memory types Mojo memory types are pointers and views that provide non-owning access to memory. A single `Pointer` type covers both safe and unsafe use: rather than splitting the guarantees across two types, Mojo marks unsafety on the individual *operation*. Dereferencing a pointer is safe, while operations with requirements the compiler can't check for you, such as unchecked offsets, aliasing casts, and overwriting memory, carry an `unsafe_` prefix. Every `Pointer` is non-nullable; use `Optional[Pointer]` to model a pointer that may be absent. | Type | What it is | |----------------------------------------------------------------|----------------------------------------------------| | [`Pointer`](/docs/std/memory/pointer/Pointer/) | Non-nullable pointer to one or more values. | | [`Span`](/docs/std/collections/span/Span/) | Non-owning view of contiguous data. | | [`AddressSpace`](/docs/std/memory/address_space/AddressSpace/) | Identifies where memory lives, such as CPU or GPU. | `OpaquePointer`, `OptionalPointer`, and the `MutX` and `ImmX` forms are aliases of `Pointer`. `UnsafePointer` is a deprecated alias for `Pointer`, kept for code written before the two types were unified. For an overview of pointer types, see [Intro to pointers](/docs/manual/pointers/). For memory allocation and pointer usage, see [Using pointers](/docs/manual/pointers/using-pointers). ## Other built-in types Mojo includes several built-in types that don't fit into the above categories but are still fundamental to the language: | Type | What it is | |---------------------------------------------------|-----------------------------------------------------------| | [`Bool`](/docs/std/builtin/bool/Bool/) | Boolean value, `True` or `False`. Backed by a 1-bit type. | | [`Error`](/docs/std/builtin/error/Error/) | A runtime error raised by a `raises` function. | | [`Never`](/docs/std/builtin/type_aliases/#never) | The type of an expression that never produces a value. | | [`NoneType`](/docs/std/builtin/none/NoneType/) | The type whose only value is `None`. | | [`Slice`](/docs/std/builtin/builtin_slice/Slice/) | The `start:end:step` descriptor a subscript produces. | ### Bool A `Bool` struct is backed by a 1-bit value, not a `SIMD` alias. Its literals are `True` and `False`. Any type that conforms to `Boolable` provides a `Bool` representation that can act as a condition: ```mojo var flag = True if flag: print("yes") # yes ``` ### Error `Error` is Mojo's default error type. A function marked `raises` raises `Error` unless it declares another type (`raises T`): ```mojo def parse(s: String) raises -> Int: raise Error("invalid input") ``` ### Never `Never` is the type of an expression that never produces a value, such as a call that always aborts or a loop that never terminates. Values of type `Never` can't exist. The compiler treats any code that follows as unreachable. `Never` is a compiler type (`!kgen.never`), not a `struct`. ```mojo from std.os import abort def fatal() -> Never: abort() # never returns ``` ### None `None` is the only value of type `NoneType`. A function with no declared return type returns `None`: ```mojo def implicit_greet(name: String): print("hello", name) # returns None implicitly def explicit_greet(name: String) -> None: print("hello", name) # returns None explicitly ``` ### Slice A `Slice` holds `start`, `end`, and `step`, each an `Optional[Int]`. It's the descriptor that subscript syntax with colons produces; it carries no data of its own. What subscripting returns depends on the type being indexed. A contiguous `List` slice yields a `Span` view; a strided slice yields a new `List`. ```mojo var items: List[Int] = [0, 1, 2, 3, 4, 5] var middle = items[1:4] # Span view, [1, 2, 3] var strided = items[::2] # new List, [0, 2, 4] ``` For slice syntax, see [Expressions](/docs/reference/expressions/#slices). --- ## Compilation targets Mojo compiles code for a range of targets, from your local machine to other CPUs, operating systems, and GPUs. You can inspect what the compiler supports, choose a target configuration, and generate code for that target. _Compilation targets_ describe where and how your program runs. They define the platform, CPU, features, and optional accelerators used during code generation, for both native and cross-compilation workflows, including GPU-enabled (_heterogeneous builds_). The Mojo command line compiler lets you inspect your current platform, select a target configuration, and generate code for that target. Use it to build for your own system or target other CPUs, operating systems, and accelerators. :::caution Work in progress Cross-compilation support is still in development. You can query targets, cross-compile to object files and assembly, and target GPU architectures. Producing a fully linked cross-compiled executable requires an external linker for the target platform. See [Emit options](#emit-options) for details on what works today. ::: ## Query your system and available targets Before setting compilation or cross-compilation flags, check which targets the compiler supports and what it detects on your system. These commands list available targets and show how the compiler configures your current machine. Use these commands to understand and choose the components of a target, including the target triple (architecture, vendor, OS), CPU, features, and accelerators. :::note A target triple is a string that identifies the target platform. It lists an architecture, vendor, and operating system. ::: ### Effective target The effective target is the configuration the compiler uses for your current system when you don't set target flags. Print the full target configuration for your system: ```sh mojo build --print-effective-target ``` Sample output on an Apple M4 MacBook Pro. The features are truncated in this example to save space: ```output Effective target configuration: --target-triple arm64-apple-darwin25.3.0 --target-cpu apple-m4 --target-features +aes,+bf16,+complxnum,+crc,+dotprod,+fp-armv8,... --target-accelerator metal:4 ``` This output shows the flags that reproduce your host configuration. Use it to see what the compiler assumes when you don't set target flags. ### Supported targets List the target architectures the compiler can generate code for. Use this command to see which architectures are available before selecting a target or composing a target triple. ```sh mojo build --print-supported-targets ``` For example: ```output Registered Targets: arm64 - ARM64 (little endian) arm64_32 - ARM64 (little endian ILP32) aarch64 - AArch64 (little endian) aarch64_32 - AArch64 (little endian ILP32) aarch64_be - AArch64 (big endian) r600 - AMD GPUs HD2XXX-HD6XXX amdgcn - AMD GCN GPUs hexagon - Hexagon ... ``` ### Supported target CPUs List valid CPU names for a given target triple. Set `--target-triple` to select the target triple and narrow the results. ```sh mojo build --print-supported-cpus \ --target-triple=aarch64-apple-macosx ``` For example: ```output Available CPUs for target aarch64-apple-macosx: a64fx ampere1 apple-a10 apple-a11 apple-m1 apple-m4 ... ``` ### Supported accelerators List the accelerator architectures the compiler can target. ```sh mojo build --print-supported-accelerators ``` ```output Supported Accelerator Architectures: NVIDIA (CUDA): sm_52 - Maxwell (GTX 970) sm_60 - Pascal (Tesla P100) sm_90 - Hopper (H100) ... AMD (ROCm/HIP): gfx942 - CDNA3 (MI300X) mi300x - (alias) -> gfx942 ... Apple Silicon GPU: apple-m1 - Apple M1 apple-m2 - Apple M2 ... Other: cuda - Generic CUDA ``` ## How Mojo describes a build target When the compiler generates machine code, it needs a few key details about the hardware it targets: - The **architecture** defines the base instruction set, such as x86-64 or AArch64. - The **CPU model** adds processor-specific behavior and may enable instructions beyond the base. - The **feature set** controls individual hardware capabilities that can be enabled or disabled, such as AVX-512 or Neon. For accelerator targets, one more detail applies: - The **accelerator architecture** identifies the GPU or other accelerator to generate device code for. If you don't set these explicitly, the compiler uses your host system. ### Target triples A target triple identifies the platform you're compiling for. It lists the architecture, vendor, and operating system in a single value: ```text x86_64-unknown-linux-gnu aarch64-apple-macosx ``` The triple sets the overall execution environment and binary conventions. It's the starting point for cross-compilation and works with both flag sets described in the next section. ## Two ways to set your target Mojo provides two sets of flags to specify target hardware. They reach the same result through different interfaces, and you can't mix them in one command. These are Mojo target flags and GCC/Clang-compatible flags. ### Mojo target flags These flags let you set the triple, CPU, and features directly. | Flag | Purpose | |------------------------|---------------------------------| | `--target-triple` | Platform (arch + vendor + OS) | | `--target-cpu` | Specific processor model | | `--target-features` | Individual feature toggles | | `--target-accelerator` | GPU or accelerator architecture | :::note When cross-compiling with Mojo target flags, set `--target-cpu` with `--target-triple`. The CPU defaults to your host processor, which may not be valid for the target architecture. Omitting `--target-cpu` when cross-compiling to a different architecture produces an error such as `failed to create target info: unknown target CPU 'apple-m4'`. ::: For example: ```sh mojo build --target-triple aarch64-unknown-linux-gnu \ --target-cpu cortex-a72 \ --emit object -o myapp.o myapp.mojo ``` Use `--target-features` to enable or disable individual hardware extensions. ```sh mojo build --target-triple x86_64-unknown-linux-gnu \ --target-cpu x86-64-v3 \ --target-features "+avx512f" \ --emit object -o myapp.o myapp.mojo ``` ### GCC/Clang-compatible flags Mojo supports the same `--march`, `--mcpu`, and `--mtune` flags used in GCC and Clang. These flags follow the behavior documented in the GCC manual and work as they do in `clang`. | Flag | Purpose | |-----------|--------------------------------------------------| | `--march` | Architecture or CPU subtype to generate code for | | `--mcpu` | CPU model (sets architecture and tuning) | | `--mtune` | Optimization hint for a specific processor | For example: ```sh mojo build --target-triple x86_64-unknown-linux-gnu \ --mcpu=haswell \ --emit object -o myapp.o myapp.mojo ``` **`--march`** controls which instructions the compiler can use. Code compiled with `--march=skylake-avx512` can use AVX-512 instructions, but it won't run on hardware that lacks them. **`--mcpu`** sets both the architecture and tuning from a single CPU name. **`--mtune`** guides optimization without changing which instructions the compiler uses. It tells the compiler to prefer instruction sequences that run faster on the given processor. The code still runs correctly on other processors with the same instruction support. :::note Known issue When using `--mcpu` or `--march` to cross-compile from a host with a different architecture, the compiler may print warnings about unrecognized features. These warnings are harmless — the compiler ignores the unsupported features and the output is correct. This will be fixed in a future release. ::: The `--march` flag supports extension syntax for adding features inline: ```sh mojo build --target-triple x86_64-unknown-linux-gnu \ --march=x86-64-v3+avx512f \ --emit asm -o myapp.s myapp.mojo ``` :::caution Architecture-specific behavior The exact relationship between `--march` and `--mcpu` varies by target architecture, matching GCC/Clang conventions: - **x86**: `--march` or `--mcpu` specifies a CPU subtype like `skylake-avx512`. With `--mcpu=generic`, `--march` is treated as an architecture baseline. - **AArch64**: `--march` sets the base architecture (like `armv8.2-a`), `--mcpu` sets the specific CPU (like `neoverse-n1`). If you only set the architecture, the CPU defaults to `generic`. - **ARM**: `--march` sets the base architecture, `--mcpu` sets the specific CPU. If you only set the architecture, the default CPU for that architecture is used. ::: ### ⚠️ Don't mix the two families {#dont-mix-the-two-families} The Mojo compiler enforces a clear separation between these flag families. Using `--target-cpu` or `--target-features` with `--march` or `--mcpu` in the same command produces an error: ```sh # This fails: mojo build --target-cpu=haswell --mcpu=skylake myapp.mojo ``` Error: ```output error: --target-cpu cannot be used with --march or --mcpu; use either --target-cpu/--target-features or --march/--mcpu/--mtune ``` Pick one family and use it consistently. Both produce the same result for the same hardware. ### Shared flags Two flags work with both families: - `--target-triple` is always valid and is typically required for cross-compilation, regardless of which family you use. - `--target-accelerator` is always valid and is used to target GPUs with either family. ## Accelerator targets Mojo supports _heterogeneous builds_ that generate host code for the CPU and device code for a GPU in a single build. Use `--target-accelerator` to specify the GPU architecture: ```sh mojo build --target-accelerator=sm_90 myapp.mojo ``` For NVIDIA and AMD targets, use a prefix to select the platform: ```sh mojo build --target-accelerator=nvidia:sm_90 myapp.mojo # NVIDIA H100 mojo build --target-accelerator=amdgpu:gfx942 myapp.mojo # AMD MI300X ``` When you use `--emit asm` with a GPU target, the compiler produces a separate file for each kernel alongside the host assembly: `.ptx` for NVIDIA, `.amdgcn` for AMD, and `.ll` for Metal. ## Cross-compilation in practice ### Generate an object file for another platform ```sh mojo build --target-triple aarch64-unknown-linux-gnu \ --target-cpu cortex-a72 \ --emit object -o myapp.o myapp.mojo ``` This produces an object file for the target platform. Link it with a toolchain for that platform. ### Generate assembly for inspection or external toolchains ```sh mojo build --target-triple x86_64-unknown-linux-gnu \ --emit asm -o myapp.s myapp.mojo ``` This produces assembly for the target platform. Use it for inspection or pass it to an external toolchain for further processing. ### Target a specific CPU with tuning ```sh mojo build --target-triple x86_64-unknown-linux-gnu \ --march=x86-64 --mcpu=haswell --mtune=skylake \ --emit object -o myapp.o myapp.mojo ``` This generates code for the Haswell instruction set and optimizes it for Skylake. ### GPU kernel compilation ```sh mojo build --target-accelerator=nvidia:sm_90 myapp.mojo ``` This compiles GPU kernels for the specified accelerator and includes them with the host build. :::caution Runtime dependencies Cross-compiled binaries don't include external libraries. This includes Python libraries, C libraries, and Modular runtime libraries. The target environment must provide all runtime dependencies your program needs. ::: ## Emit options The `--emit` flag controls the output `mojo build` produces. These options are essential for cross-compilation because you can't yet produce linked executables with the Mojo compiler. | Value | Output | Status | |-----------------|-----------------------------|--------| | `exe` (default) | Executable binary | Native | | `shared-lib` | Shared (dynamic) library | Native | | `object` | Object file (experimental) | Both | | `llvm` | Unoptimized LLVM IR | Both | | `llvm-bitcode` | Unoptimized LLVM IR bitcode | Both | | `asm` | Assembly (+ GPU sidecars) | Both | ### What's working Outputs that don't require linking work with any supported target: - `--emit object` — produces a relocatable object file for the target - `--emit asm` — produces assembly for the target - `--emit llvm` — produces LLVM IR configured for the target - `--emit llvm-bitcode` — produces LLVM bitcode for the target Outputs that require linking need a linker for the target platform, which Mojo doesn't provide and aren't working: - `--emit exe` — fails at the link step when cross-compiling - `--emit shared-lib` — fails at the link step when cross-compiling To produce a cross-compiled executable or shared library, generate an object file and link it with a toolchain for your target platform. ## Call a Mojo shared library from C or C++ You can compile Mojo code into a shared library and call it from a program written in another language, such as C or C++, through the C ABI. ### Build and export Build the shared library with `--emit shared-lib`: ```sh mojo build mylib.mojo --emit shared-lib -o libmylib.so ``` (Use a `.dylib` extension on macOS.) Mark each function you want to call from the host with the [`@export`](/docs/reference/decorators/export/) decorator, giving it a name that's a valid C identifier and the `abi("C")` effect so it follows the C calling convention. ### Initialize the Mojo runtime When a Mojo program starts from its own `main()` function, compiler-generated startup code initializes the Mojo runtime — the thread pool that parallel APIs such as [`parallelize()`](https://max.modular.com/api/mojo/max/algorithm/backend/cpu/parallelize/parallelize/) depend on. When the process `main()` belongs to a C or C++ host instead, that startup code never runs, so the runtime is never initialized. An exported function that then uses a runtime-dependent API crashes with a segmentation fault. To fix this, call [`initialize_runtime()`](/docs/std/runtime/initialize_runtime/) before any runtime-dependent Mojo code executes. The call is idempotent and inexpensive when the runtime is already initialized, and one initialization covers all threads in the process. There are two common patterns: - Call `initialize_runtime()` at the start of every exported function. This is the simplest approach and imposes no calling contract on the host program. - Export a dedicated initialization function and require the host to call it once before anything else: ```mojo from std.runtime import initialize_runtime @export("mylib_init") def mylib_init() abi("C"): initialize_runtime() ``` ### Complete example The following Mojo library exports one function that fills a list in parallel and returns a checksum: ```mojo title="mylib.mojo" from max.algorithm import parallelize from std.runtime import initialize_runtime @export("parallel_sum") def parallel_sum(n: Int64) abi("C") -> Int64: initialize_runtime() var count = Int(n) var results = List[Int64](length=count, fill=0) def fill(i: Int) {mut results}: results[i] = Int64(i) parallelize(fill, count) var total = Int64(0) for r in results: total += r return total ``` A C host program that calls it: ```c title="main.c" #include extern long long parallel_sum(long long n); int main(void) { printf("sum=%lld\n", parallel_sum(1000)); return 0; } ``` Build and run on Linux: ```sh mojo build mylib.mojo --emit shared-lib -o libmylib.so cc main.c -o main -L. -lmylib -Wl,-rpath,'$ORIGIN' ./main ``` On macOS: ```sh mojo build mylib.mojo --emit shared-lib -o libmylib.dylib install_name_tool -id @rpath/libmylib.dylib libmylib.dylib cc main.c -o main -L. -lmylib -Wl,-rpath,@loader_path ./main ``` Both print: ```output sum=499500 ``` :::note Differences from a Mojo executable Without a Mojo `main()` function, some process-level setup that Mojo executables perform doesn't happen: - `sys.argv()` isn't populated with the host program's arguments. - The signal handler that prints a stack trace on a crash isn't installed. - The runtime, once initialized, remains alive until the process exits; there is no API to shut it down. - The host program's dynamic loader must be able to locate the Modular runtime libraries that the shared library depends on (for example, through the rpath entries embedded in the shared library). ::: --- ## Debugging The Mojo extension for Visual Studio Code enables you to use VS Code's built-in debugger with Mojo code. This page describes the features available through the VS Code Mojo extension, as well as current limitations of the Mojo debugger. You can install the Mojo extension from either the [Visual Studio Code Marketplace](https://marketplace.visualstudio.com/items?itemName=modular-mojotools.vscode-mojo) or the [Open VSX Registry](https://open-vsx.org/extension/modular-mojotools/vscode-mojo). To use the Mojo extension, you must also [install the `mojo` package](/install/)—or, if you're developing for the MAX framework, [install the `modular` package](https://max.modular.com/packages/), which includes the `mojo` package. :::note The Mojo extension relies on the Python extension for locating your Python environment. In some cases, this appears to default to your globally-installed environment, even when a virtual environment exists. If the Mojo extension cannot find your SDK installation, try invoking the "Python: Set Project Environment" command and selecting your virtual environment. ::: For complete coverage of VS Code's debugging features, see [Debugging in Visual Studio Code](https://code.visualstudio.com/docs/editor/debugging). The `mojo` package includes the [LLDB debugger](https://lldb.llvm.org/) and a Mojo LLDB plugin. Together these provide the low-level debugging interface for the Mojo extension. You can also use the `mojo debug` command to start a command-line debugging session using LLDB or to launch a Mojo debugging session in VS Code. The `mojo` package also includes support for debugging Mojo programs running on GPU. This requires some extra software and configuration. Currently GPU debugging only works with NVIDIA GPUs. For details, see [GPU debugging](https://max.modular.com/gpu/debugging/). ## Start debugging There are several ways to start a debug session in VS Code. To start debugging, you'll need to have a Mojo project to debug. There are a number of examples ranging from simple to complex in [our GitHub repo](https://github.com/modular/modular/tree/mojo/v1.1.0/Mojo/examples). :::note VS Code veteran? If you're already familiar with debugging in VS Code, the material in this section will mostly be review. You might want to skip ahead to [Launch configurations](#launch-configurations) or see [Using the debugger](#using-the-debugger) for notes on the features supported in the Mojo debugger. ::: ### Quick run or debug If your active editor tab contains a Mojo file with an `def main()` entry point, one of the quickest ways to run or debug it is using the **Run or Debug** button in the Editor toolbar. ![](images/quick-run-or-debug-button.png) To start debugging the current file: - Open the **Run or Debug** dropdown menu and choose **Debug Mojo File** or **Debug Mojo File in Dedicated Terminal**. ![](images/quick-run-or-debug-menu.png) The two debug configurations differ in how they handle input and output: - **Debug Mojo File** launches the Mojo program detached from any terminal. Standard output and standard error output for the program are displayed in the **Debug Console**. You can't write to the program's standard input, but you can see the program's output and interact with the debugger in a single location. - **Debug Mojo File in Dedicated Terminal** creates a new instance of VS Code's integrated terminal and attaches the program's input and output to the terminal. This lets you interact with the program's standard input, standard output and standard error output in the terminal, while the **Debug Console** is used only for interactions with the debugger. The **Run or Debug** button uses predefined launch configurations. There's currently no way to modify the `args`, `env`, `cwd` or other settings for programs launched with the **Run or Debug** configurations. If you need to customize any of these things, see [Edit launch configurations](#edit-launch-configurations). After you choose one of the debug configurations, the button updates to show the debug symbol. Click the button to re-run the previous configuration. ![](images/quick-run-or-debug-button-debug.png). ### Run and Debug view The **Run and Debug** view includes a button to launch debug sessions and a menu to select debug configurations. It also has areas to display current variables, watch expressions, the current call stack, and breakpoints.
![](images/run-and-debug-view.png)
Figure 1. Run and Debug view
To open **Run and Debug** view, click the **Run and Debug** icon in the **Activity Bar** (on the left side of the VS Code window) or press Control+Shift+D (Command+Shift+D on macOS). ![](images/run-and-debug-icon.png) If you haven't created any launch configurations in the current project, VS Code shows the **Run start view**.
![](images/run-start-view.png)
Figure 2. Run start view
If you've already launched a debug session or created a `launch.json` file to define launch configurations, you'll see the **Launch configurations** menu, which lets you choose configurations and start debug sessions:
![](images/launch-configuration-menu.png)
Figure 3. Launch configurations menu
### Other ways to start a debug session There are a number of other ways to start a debug session. #### Launching from the Command Palette If you have a Mojo file open in your active editor, you can also start a debug session from the **Command Palette**. 1. Click **View** > **Command Palette** or press Control+Shift+P (Command+Shift+P on macOS). 2. Enter "Mojo" at the prompt to bring up the Mojo commands. You should see the same debug configurations described in [Quick run or debug](#quick-run-or-debug). #### Launch from the File Explorer To launch a debug session from the **File Explorer** view: 1. Right-click on a Mojo file. 2. Select a Mojo debug configuration. You should see the same debug configurations described in [Quick run or debug](#quick-run-or-debug). #### Debug with F5 Press F5 to start a debug session using the current debug configuration. If you don't have any existing debug configurations available to select, and your active editor contains a Mojo file with an `def main()` entry point, pressing F5 will launch and debug the current file using the **Debug Mojo File** action described in [Quick run or debug](#quick-run-or-debug). ## Starting the debugger from the command line Use the `mojo debug` command to start a debug session from the command line. You can choose from two debugging interfaces: - With the `--vscode` flag, `mojo debug` starts a debug session on VS Code if it's running and the Mojo extension is enabled. - Without the `--vscode` flag, `mojo debug` starts a command-line [LLDB debugger](https://lldb.llvm.org/) session. You can choose to build and debug a Mojo file, run and debug a compiled binary, or to attach the debugger to a running process. :::note Environment variables When you debug a program from the command line using `--vscode`, the program runs with the environment variables set in the terminal. When launching from inside VS Code via the GUI, the environment is defined by the VS Code [launch configuration](#launch-configurations). ::: For a full list of command-line options, see the [`mojo debug` reference page](/docs/cli/debug). ### Start a debug session from the command line With VS Code open, run the following command (either from VS Code's integrated terminal or an external shell): ```bash mojo debug --vscode myproject.mojo ``` Or to debug a compiled binary: ```bash mojo debug --vscode myproject ``` For best results, build with the `-O0 -g` command-line options when you build a binary that you intend to debug—this produces a binary with full debug info. (When you call `mojo debug` on a Mojo source file, it includes debug information by default.) See the [`mojo build` reference page](/docs/cli/build/) for details on compilation options. ### Attach the debugger to a running process from the command line You can also attach the debugger to a running process by specifying either the process ID or process name on the command line: ```bash mojo debug --vscode --pid ``` Or: ```bash mojo debug --vscode --process-name ``` ## Launch configurations VS Code *launch configurations* let you define setup information for debugging your applications. The Mojo debugger provides the following launch configuration templates: - Debug current Mojo file. Launches and debugs the Mojo file in the active editor tab. Effectively the same as the **Debug Mojo File** action described in [Quick run or debug](#quick-run-or-debug), but with more configuration options. - Debug Mojo file. Like the previous entry, except that it identifies a specific file to launch and debug, no matter what file is displayed in the active editor. - Debug binary. This configuration operates on a prebuilt binary, which could be written in any mixture of languages supported by LLDB (Mojo, C, C++, etc.). You need to set the `program` field to the path of your binary. - Attach to process. Launches a debug session attached to a running process. On launch, you choose the process you want to debug from a list of running processes. You can edit any of these templates to customize them. All VS Code launch configurations must contain the following attributes: - `name`. The name of the launch configuration, which shows up in the UI (for example, "Run current Mojo file"). - `request`. Can be either `launch` (to run a program from VS Code) or `attach` (to attach to and debug a running file). - `type`. Use `mojo-lldb` for the Mojo debugger. Use `mojo-cuda-gdb` to debug on GPU. In addition, Mojo launch configurations can contain the following attributes: - `args`. Any command-line arguments to be passed to the program. - `cwd`. The current working directory to run the program in. - `description`. A longer description of the configuration, not shown in the UI. - `env`. Environment variables to be set before running the program. - `mojoFile`. Path to a Mojo file to launch and debug. - `pid`. Process ID of the running process to attach to. - `program`. Path to a compiled binary to launch and debug, or the program to attach to. - `runInTerminal`. True to run the program with a dedicated terminal, which allows the program to receive standard input from the terminal. False to run the program with its output directed to the **Debug Console**. Mojo GPU launch configurations can contain the following attributes: - `breakOnLaunch`. Set to true to automatically break when a GPU kernel launches. - `initCommands`. An array of commands to issue to the debugger on startup. To use the classic CUDA-GDB debugger backend, add the following lines to your configuration: ```json "initCommands": [ "set environment CUDBG_USE_LEGACY_DEBUGGER=1" ], ``` - `legacyDebugger`. Set to true to use the classic debugger backend. If configuration is a `launch` request, the configuration must include either the `mojoFile` or `program` attribute. For `attach` requests, the configuration must include either the `pid` or `program` attribute. VS Code performs variable substitution on the launch configurations. You can use `${workspaceFolder}` to substitute the path to the current workspace, and `${file}` to represent the file in the active editor tab. For a complete list of variables, see the VS Code [Variables reference](https://code.visualstudio.com/docs/editor/variables-reference). For more information, see the VS Code documentation for [Launch configurations](https://code.visualstudio.com/docs/editor/debugging#_launch-configurations). :::note Compilation options Mojo launch configurations don't allow you to specify compilation options. If you need to specify compilation options, you can build the binary using [`mojo build`](/docs/cli/build), then use a launch configuration with the `program` option to launch the compiled binary. Or if you [start the debugger from the command line](#starting-the-debugger-from-the-command-line), you can pass compilation options to the `mojo debug` command. ::: ### Edit launch configurations To edit launch configurations: 1. If the **Run and Debug** view isn't already open, click the **Run and Debug** icon in the **Activity Bar** (on the left side of the VS Code window) or press Control+Shift+D (Command+Shift+D on macOS). ![](images/run-and-debug-icon.png) 2. Create or open the `launch.json` file: 1. If you see the **Run start view**, click **create a launch.json file**. 2. If you already have launch configurations set up, click the gear icon next to the **Launch configurations** menu. ![](images/launch-configuration-menu.png) 3. Select **Mojo** from the list of debuggers. VS Code opens the new `launch.json` file in an editor tab, with templates for some common debug actions. Click **Add configuration** to add a new configuration template. ## Using the debugger When a debug session is running, use the debug toolbar to pause, continue, and step through the program. ![](images/debug-toolbar.png) The buttons on the toolbar are: - **Continue/Pause**: If the program is stopped, resume the normal execution of the program up to the next breakpoint, signal or crash. Otherwise, pause all the threads of the program at once. - **Step Over**: Execute the next line of code without stopping at function calls. - **Step Into**: Execute the next line of code and stop at the first function call. If the program is stopped just before a function call, steps into the function so you can step through it line-by-line. - **Step Out**: Finish the execution of the current function and stop right after returning to the parent function. - **Restart**: If this is a `launch` session, terminate the current program and restart the debug session. Otherwise, detach from the target process and reattach to it. - **Stop**: If this is a `launch` session, terminate the current program. Otherwise, detach from the target process without killing it. The debugger currently has the following limitations: - No support for breaking automatically on Mojo errors. - When stepping out of a function, the returned value is not displayed. - LLDB doesn't support stopping or resuming individual threads. ### Breakpoints The Mojo debugger supports setting [standard breakpoints](https://code.visualstudio.com/docs/editor/debugging#_breakpoints), [logpoints](https://code.visualstudio.com/docs/editor/debugging#_logpoints), [function breakpoints](https://code.visualstudio.com/docs/editor/debugging#_function-breakpoints), [data breakpoints](https://code.visualstudio.com/docs/editor/debugging#_data-breakpoints), and [triggered breakpoints](https://code.visualstudio.com/docs/editor/debugging#_triggered-breakpoints), as described in the VS Code documentation. The Mojo debugger also supports *error breakpoints* (also known as "break on raise"), which break whenever a `raise` statement is executed. When debugging Mojo code, the debugger doesn't support conditional breakpoints based on an expression (it does support hit counts, which VS Code classifies as a kind of conditional breakpoint). When editing a breakpoint, you're offered four options: - **Expression**. Set a conditional breakpoint (not currently supported). - **Hit Count**. Add a hit count to a breakpoint (supported). - **Log Message**. Add a logpoint (supported) - **Wait for Breakpoint**. Add a triggered breakpoint (supported). #### Set a hit count breakpoint A hit count breakpoint is a breakpoint that only breaks execution after the debugger hits it a specified number of times. To add a hit count breakpoint: 1. Right click in the left gutter of the editor where you want to place the breakpoint, and select **Add Conditional Breakpoint.** 2. Select **Hit Count** from the menu and enter the desired hit count. To change an existing breakpoint to a hit count breakpoint: 1. Right click on the breakpoint in the left gutter of the editor and select **Edit breakpoint**. 2. Select **Hit Count** from the menu and enter the desired hit count. You can also edit a breakpoint from the **Breakpoints** section of the **Run and Debug** view: - Right-click on the breakpoint and select **Edit Condition**, or, - Click the **Edit Condition** icon next to the breakpoint. This brings up the same menu, **next to the breakpoint in the editor tab**. #### Enable error breakpoints You can enable and disable error breakpoints in VS Code by selecting "Mojo Raise" in the **Breakpoints** section of the **Run and Debug** view. If enabled during debugging, executing a `raise` statement causes the debugger to stop execution and highlight the line of code where the error was raised. ![VS Code window showing a program paused in the debugger with the Run and Debug view visible. The program is paused at a raise statement.](images/break-on-raise.png) ### View local variables When a program is paused in the debugger, the editor shows local variable values inline. You can also find them in the **Variables** section of the **Run and Debug** view.
![VS Code window showing a program paused in the debugger, with the variables sections of the Run and Debug view visible. The edit shows three functions (nested2, nested1, and main). The program is paused at a breakpoint in nested2.](images/debugger-variables.png)
Figure 4. Local variable values displayed in the debugger
### View the call stack When a program is paused in the debugger, the **Run and Debug** view shows the current call stack. (You may see multiple call stacks, one for each active thread in the program.)
![VS Code window showing a program paused in the debugger, with the call stack and variables sections of the Run and Debug view visible. The call stack shows three functions (nested2, nested1, and main). The program is paused at a breakpoint in nested2; the parent function nested1 is selected in the call stack, and editor highlights the current line in nested1 (the call to nested2()).](images/debugger-call-stack-nested1.png)
Figure 5. Call stack in Run and Debug view
The **Call Stack** section of the Run and Debug view shows a stack frame for each function call in the current call stack. Clicking on the name of the function highlights the current line in that function. For example, in Figure 5, the program is paused at a breakpoint in `nested2()`, but the parent function, `nested1()` is selected in the call stack. The editor highlights the current line in `nested1()` (that is, the call to `nested2()`) and shows the current local variable values for `nested1()`. ### Use the Debug Console The **Debug Console** gives you a command-line interface to the debugger. The **Debug Console** processes LLDB commands and Mojo expressions. Anything prefixed with a colon (`:`) is treated as an LLDB command. Any other input is treated as an expression. Currently Mojo expressions are limited to inspecting variables and their fields. The console also supports subscript notation (`vector[index]`) for certain data structures in the standard library, including `List` and `SIMD`. In the future, we intend to provide a way for arbitrary data structures to support subscript notation in the **Debug Console**. :::note The **Debug Console** only accepts input when the program is paused. ::: ## Tips and tricks There are several features in the standard library that aren't directly related to the debugger, but which can help you debug your programs. These include: - Programmatic breakpoints. - Setting parameters from the Mojo command line. ### Set a programmatic breakpoint To break at a specific point in your code, you can use the built-in [`breakpoint()`](/docs/std/builtin/breakpoint/breakpoint/) function: ```mojo if some_value.is_valid(): do_the_right_thing() else: # We should never get here! breakpoint() ``` If you have VS Code open and run this code in debug mode (either using VS Code or `mojo debug`), hitting the `breakpoint()` call causes an error, which triggers the debugger. :::note Assertions The [`testing`](/docs/std/testing/testing/) module includes a number of ways to specify assertions. Assertions also trigger an error, so can open the debugger in the same way that a `breakpoint()` call will. ::: ### Set parameters from the Mojo command line You can use the [`sys`](/docs/std/sys/) module to retrieve parameter values specified on the Mojo command line. Among other things, this is an easy way to switch debugging logic on and off. For example: ```mojo from std.sys import is_defined def some_function_with_issues(): # ... comptime if is_defined["DEBUG_ME"](): breakpoint() ``` To activate this code, use the [`-D` command-line option](/docs/cli/debug#compilation-options) to define `DEBUG_ME`: ```bash mojo debug -D DEBUG_ME main.mojo ``` The `is_defined()` function returns a compile-time true or false value based on whether the specified name is defined. Since the `breakpoint()` call is inside a [`comptime if` statement](/docs/manual/metaprogramming/comptime-evaluation/#comptime-if), it is only included in the compiled code when the `DEBUG_ME` name is defined on the command line. ## Troubleshooting ### `error: can't connect to the RPC debug server socket` If using `mojo debug --vscode` gives you the message `error: can't connect to the RPC debug server socket: Connection refused`, try the following possible fixes: - Make sure VS Code is open. - If VS Code is already open, try restarting VS Code. - If there are other VS Code windows open, try closing them and then restarting. This error can sometimes occur when multiple windows have opened and closed in certain orders. ### `error: couldn't get a valid response from the RPC server` If using `mojo debug --vscode` gives you the message `error: couldn't get a valid response from the RPC server`, try the following possible fixes: - Make sure VS Code is open to a valid Mojo codebase. This error can sometimes happen if the VS Code window is open to some other codebase. - If there are multiple VS Code windows open, try closing all but the one you wish to debug in. - Restart VS Code. - Reinstall the SDK and restart VSCode. - If you are working on a development version of the SDK, make sure that all SDK tools are properly built with your build system, and then reload VS Code. - As a last resort, restarting your entire computer can fix this problem. If these steps don't help, please file an issue. We'd love your help identifying possible causes and fixes! --- ## Mojo compilation feature toggles Mojo provides several mechanisms for compile-time feature gating and configuration: - **Compile-time defines** (`-D` and `sys.defines`): pass values from the command line into Mojo code - **Compile-time conditionals and platform detection** (`comptime if`, `comptime assert`, `sys.info`): branch or halt compilation based on compile-time conditions - **Debug and optimization gating** (`debug_assert()`): control debug-only behavior and runtime checks ## Compile-time conditionals ### Using `comptime assert` to establish preconditions `comptime assert` halts compilation when its condition evaluates to `False`. Unlike a runtime assertion, it executes during compilation and produces a compiler error with your message. Use `comptime assert` to declare compile-time preconditions on parameters or compilation targets. For example, say you call a GPU-specific function from a CPU build: ```mojo from std.sys import is_gpu def gpu_kernel(): # Called from a CPU build comptime assert is_gpu(), "this function requires a GPU target" # ... GPU-specific code ``` When you call `gpu_kernel()`, the compiler prints a `constraint failed:` note with your message, pointing at the assert: ```text note: constraint failed: this function requires a GPU target comptime assert is_gpu(), "this function requires a GPU target" ^ ``` ### Feature gating with `comptime if` Use `comptime if` to select code paths at compile time. The condition must be *parameter-evaluable*, that is, the compiler must reason about it and it can depend on `comptime` values and parameter expressions: Call: ```sh mojo run -Dmode=release hello.mojo ``` Code: ```mojo from std.sys import get_defined_string def main(): comptime mode = get_defined_string["mode", "debug"]() comptime if mode == "release": print("optimized path") else: print("debug path with extra checks") ``` ## Compile-time defines The `-D` flag passes key-value pairs from the command line into Mojo code. The `std.sys.defines` module reads them. Call: ```sh mojo run -Dmode=release -Dverbose -Dmax_threads=8 hello.mojo # or mojo run -D mode=release -D verbose -D max_threads=8 hello.mojo ``` You can write either `-Dkey=value` or `-D key=value`. Keys and values must be joined with `=`. Supported forms include: - `-D KEY=VALUE`: the value is parsed as a string, integer, or boolean, depending on which `get_defined_*[]()` function reads it - `-D KEY`: defines a flag with no value. `is_defined[]()` returns `True`. Use `is_defined[]()` for presence checks - `-D KEY=42`: numeric values can be read with `get_defined_int[]()` The `sys.defines` module exposes several functions for reading compile-time defines. All define names are compile-time `StaticString` parameters, not runtime strings. ### `is_defined[name]()` `is_defined[name]()` returns `True` when `-D name` was passed, regardless of its value. It never errors. Call: ```sh mojo -Dverbose hello.mojo ``` Code: ```mojo from std.sys import is_defined def main(): comptime if is_defined["verbose"](): print("verbose mode enabled") ``` `is_defined[name]()` is similar to C's `#ifdef`. It checks only whether a define exists. The value is ignored. Use it when any value enables the feature, or when you only care that the flag was passed. Use other `get_defined_*[]()` functions to read the value itself. ### `get_defined_bool[name, default=False]()` `get_defined_bool[name, default=False]()` returns a `Bool`. It distinguishes between "defined" and "truthy". The following values are treated as `True`: - `1` - `true`, `True`, `TRUE` - `on`, `On`, `ON` Any other assigned string value returns `False`. This function errors when the define does not provide a value. | Command | Compiler view | Result | |--------------------------------|-----------------------------|--------------------------------------------------| | `mojo -D verbose app.mojo` | `verbose` defined, no value | Error | | `mojo -D verbose=on app.mojo` | `verbose="on"` | `True` | | `mojo -D verbose=yes app.mojo` | `verbose="yes"` | `False` (`yes` is not a recognized truthy value) | | `mojo app.mojo` | define missing | `default` → `False` | ```mojo from std.sys import get_defined_bool def main(): comptime verbose = get_defined_bool["verbose"]() comptime if verbose: print("verbose mode enabled") ``` Avoid `default=True`. It reverses the meaning in a confusing way: missing values become `True`, while present-but-non-truthy values such as `-D verbose=banana` or `-D verbose=0` become `False`. If you need `default=True`, consider using `is_defined[]()` instead. It expresses intent more clearly. ### `get_defined_int[name]()` and `get_defined_int[name, default]()` `get_defined_int[name]()` returns an `Int`. If the define is missing or the value is not a valid integer, compilation fails. Use this only when the define is required. Call: ```sh mojo -D max_threads=8 app.mojo ``` Code: ```mojo from std.sys import get_defined_int def main(): comptime threads = get_defined_int["max_threads"]() print(t"Up to {threads} threads") ``` The parser accepts only base-10 integers. For example, `-D N=10` works. `-D N=0x10`, `-D N=0o10`, and `-D N=1_000` all fail at the `get_defined_int[]()` call site. The values are stored as strings and Mojo doesn't recognize those formats as integers. Non-integer values such as `-D max_threads=eight` fail the same way. If you encounter these errors, check command-line spelling and format. The defaulted version returns the provided value instead of erroring when the define is missing. Call: ```sh mojo -D max_threads=8 app.mojo ``` Code: ```mojo from std.sys import get_defined_int def main(): comptime threads = get_defined_int["max_threads", 4]() print("using", threads, "threads") ``` The default handles only missing defines. If the define exists but its value is not a valid integer, compilation still fails. ### `get_defined_string[name]()` and `get_defined_string[name, default]()` `get_defined_string[name]()` returns a `StaticString`. Compilation fails if the define is missing. Use this when the define is required. Call: ```sh mojo -D mode=release app.mojo ``` Code: ```mojo from std.sys import get_defined_string def main(): comptime mode = get_defined_string["mode"]() comptime if mode == "release": print("release build") ``` The defaulted version returns `default` instead of erroring when the define is entirely missing. Call: ```sh mojo -D mode=release app.mojo # release # or mojo app.mojo # debug (default) ``` Code: ```mojo from std.sys import get_defined_string def main(): comptime mode = get_defined_string["mode", "debug"]() comptime if mode == "release": print("release build") ``` ### `get_defined_dtype[name, default]()` `get_defined_dtype[name, default]()` returns a `DType`. A default value is required. Use this to parameterize numeric code with a user-selected type. Call: ```sh mojo -D dtype=float8_e4m3fn -D ctype=bfloat16 app.mojo ``` Code: ```mojo from std.sys import get_defined_dtype def main() raises: # ... setup for a typical matmul call comptime a_type = get_defined_dtype["dtype", DType.bfloat16]() comptime c_type = get_defined_dtype["ctype", DType.bfloat16]() matmul[a_type, c_type](a, b, c) # specialized at compile time for this dtype pair # ... continuing code ``` Unlike C-style `-D` flags, which produce preprocessor strings, Mojo treats `-D` values as first-class compile-time parameters in the type system. This allows the compiler to specialize code such as `matmul[]()` for every `DType` combination passed on the command line, without runtime branching. Values are parsed by the standard library's internal `DType` parser, which expects canonical names such as `float16`, `bfloat16`, and `float8_e4m3fn`. Misspelled or aliased names such as `fp16` and `bf16` don't necessarily produce compile-time errors at the `get_defined_dtype[]()` call site. They are parsed as an invalid dtype. An error appears later if that value is used in a context that rejects it. If you encounter these errors, check the exact spelling used on the command line. ## Platform and architecture detection The `sys.info` module provides compile-time, parameter-evaluable functions for branching on compilation targets. ### OS detection Detect the target operating system with: - `CompilationTarget.is_linux()` - `CompilationTarget.is_macos()` Mojo does not currently support Windows targets natively, so there is no Windows detection API. ### CPU detection Detect the target CPU architecture or specific Apple Silicon generation with: - `CompilationTarget.is_x86()` - `CompilationTarget.is_arm()` - `CompilationTarget.is_riscv()`, and `CompilationTarget.is_rv32()` or `CompilationTarget.is_rv64()` for a specific register width - `CompilationTarget.is_apple_silicon()` - `CompilationTarget.is_apple_m1()` through `CompilationTarget.is_apple_m5()` ### Instruction set detection Detect target instruction set extensions with APIs such as: - `CompilationTarget.has_avx512f()` - `CompilationTarget.has_neon()` RISC-V has too many extensions for a predicate apiece, so name the extension instead, using its lowercase LLVM spelling: - `CompilationTarget.has_riscv_extension["m"]()` - `CompilationTarget.has_riscv_extension["zba"]()` ### GPU and accelerator detection Check whether code is compiling *for* a specific accelerator target with: - `is_nvidia_gpu()` - `is_amd_gpu()` - `is_apple_gpu()` - `is_gpu()` Check whether the *host system* has a detected accelerator with: - `has_accelerator()` - `has_nvidia_gpu_accelerator()` - `has_amd_gpu_accelerator()` - `has_apple_gpu_accelerator()` The distinction matters: - `is_nvidia_gpu()` asks: "am I compiling for an NVIDIA GPU?" - `has_nvidia_gpu_accelerator()` asks whether NVIDIA GPU acceleration is available. This can also be true when the current compilation target is NVIDIA GPU. For example: ```mojo from std.sys import CompilationTarget def compute(): comptime if CompilationTarget.has_avx512f(): print("AVX-512 path") elif CompilationTarget.is_apple_silicon(): print("Apple Silicon path") else: print("generic path") ``` These functions report what the compiler is building for. To target a different platform, set the architecture, CPU, feature set, or accelerator from the command line. See [Compilation targets](/docs/tools/compilation) for the available flags and how to query what your toolchain supports. ## Built-in defines Mojo provides several built-in defines for controlling compilation and runtime behavior. Other than `ASSERT`, each is populated by the compiler from a driver flag. Use the driver flag rather than `-D` so the define matches the compiler's behavior. The flags are shown in the following table: | Define | Flag | Type | Values | |------------------------|---------------------------------|----------|-----------------------------------------------------| | `__OPTIMIZATION_LEVEL` | `-O` / `--optimization-level` | `Int` | `0`, `1`, `2`, `3` (default `3`) | | `__DEBUG_LEVEL` | `-g` / `--debug-level` | `String` | `"line-tables"`, `"full"` | | `__SANITIZE_ADDRESS` | `--sanitize=address` | `Int` | `0` (off, default), `1` (on) | If `-g` is omitted, `__DEBUG_LEVEL` is not injected; `DebugLevel.level` returns `"none"` as a library fallback. `--sanitize` also accepts `thread` (ThreadSanitizer), but only `--sanitize=address` injects a compile-time define. Read these values through `sys.compile`, which exposes them as the compile-time values `OptimizationLevel.level` (an `Int`), `DebugLevel.level` (a `String`), and `SanitizeAddress` (a `Bool`). For example: ```mojo from std.sys.compile import OptimizationLevel def main(): comptime if OptimizationLevel.level == 0: print("unoptimized build") ``` Mojo's `ASSERT` flag controls `debug_assert()` behavior: | Define | Flag | Type | Values | |----------|---------------------|----------|-----------------------------------------| | `ASSERT` | `-D ASSERT=` | `String` | `none`, `safe` (default), `all`, `warn` | `debug_assert()` reads this value directly. Supported assertion levels are: - `none`: disable all assertions - `safe` (default in non-debug builds): only run assertions tagged `assert_mode="safe"` - `all`: run every `debug_assert()` call - `warn`: run every assertion, but emit warnings instead of aborting For example: ```sh mojo run -D ASSERT=all hello.mojo ``` ## Using `debug_assert()` with `-D ASSERT` `debug_assert()` is a runtime assertion controlled by the `-D ASSERT` flag, which defaults to `safe`. Other debug-related settings such as `-g` and `-O` don't affect `debug_assert()` behavior. The default `safe` mode runs only assertions explicitly tagged as low-overhead: ```mojo debug_assert[assert_mode="safe"]( n >= 0, "nth: n must be non-negative", ) ``` Untagged assertions written as `debug_assert(...)` run under `-D ASSERT=warn` and `-D ASSERT=all`. Conventionally: - Tag constant-time checks such as bounds tests and integer comparisons with `assert_mode="safe"` - Leave traversals, allocations, and more expensive invariant checks untagged The plain `Bool` form always evaluates the condition, even when assertions are disabled: ```mojo # Bool form: always evaluates the condition. debug_assert(len(data) > 0, "data must not be empty") ``` :::caution Apple GPU `debug_assert()` is silently disabled on Apple GPU targets. ::: ## Debug and optimization Debug and optimization features are controlled independently; enabling one does not automatically enable the others. "Debug build" can mean several different things in Mojo. **Changes how the compiler emits output**: | Goal | Flag | Effect | |-------------------------------------------------|---------------------------------------|-----------------------------------------| | Emit full debug info (LLDB symbols) | `-g` or `--debug-level=full` | Sets `__DEBUG_LEVEL="full"` | | Emit line tables only | `-g1` or `--debug-level=line-tables` | Sets `__DEBUG_LEVEL="line-tables"` | | Disable optimization | `-O0` or `--no-optimization` | Sets `__OPTIMIZATION_LEVEL=0` | | Enable AddressSanitizer | `--sanitize=address` | Sets `__SANITIZE_ADDRESS=1` | **Changes the defines seen by Mojo code**: | Goal | Flag | Effect | |-------------------------------------------------|---------------------------------------|-----------------------------------------| | Enable all `debug_assert()` checks | `-D ASSERT=all` | Independent of `-g` and `-O` | A typical debug configuration combines several of these flags: ```sh mojo -g -O0 -D ASSERT=all app.mojo ``` The `-g` and `-O` driver flags don't affect `debug_assert()`. If you want these behaviors together, pass each flag explicitly. A typical release build requires no special flags. Running: ```sh mojo app.mojo ``` uses `-O3`, emits no debug info and disables sanitizers. One exception is `debug_assert()`. Its default mode is `safe`, so assertions tagged as always-on still execute. Pass `-D ASSERT=none` to disable them. ## Reading optimization and debug levels The compiler-defined values `__OPTIMIZATION_LEVEL` and `__DEBUG_LEVEL` are exposed through `sys.compile` as the compile-time values `OptimizationLevel` and `DebugLevel`. Use these when you need fine control: - `OptimizationLevel.level` stores an integer from `0` to `3`. - `DebugLevel.level` stores one of the strings `"none"`, `"line-tables"`, or `"full"`. ```mojo from std.sys.compile import DebugLevel def main(): comptime if DebugLevel.level == "full": print( "full debug info emitted: enabling source-aware logging" ) ``` :::note Don't confuse Mojo assert levels with MAX runtime configuration. The MAX runtime defines its own assertion system through the `MODULAR_DEBUG=assert-level=...` environment variable, with levels such as `none`, `warn`, `safe`, and `all`. These settings control MAX inference runtime assertions. `-D ASSERT` controls Mojo `debug_assert()` behavior. The two systems are independent. ::: --- ## Jupyter notebooks [Jupyter notebooks](https://jupyter.org) provide a web-based environment for creating and sharing Mojo computational documents. They combine code, results, and explanation so readers explore what you built, how you built it, and why it matters. You can run Mojo language notebooks locally or in GPU-backed Google Colab environments to accelerate workloads. For teaching, learning, and exploration, notebooks provide a hands-on, iterative workflow. This page assumes you'll work with Mojo notebooks in one of two ways: - **Google Colab** Fast setup, optional GPU acceleration, ideal for quick experiments and for learning GPU programming when you don't have a compatible GPU-enabled computer on-hand. - **Local JupyterLab** Private environment with full control of code, data, and dependencies. Both options use the same notebook model and the same Mojo cell magic. ## Using Mojo on Google Colab 1. Create a Notebook: Visit [Google Colab](https://colab.google) and create a new notebook. 2. Install Mojo: For most notebook work, the `mojo` package is all you need. This page installs `max` instead, because it includes the Mojo compiler—so `%%mojo` cells behave exactly the same—and it adds the MAX accelerator library that the [GPU examples](#using-mojo-with-gpu-support) later on this page import. For the nightly release: ```python !pip install --pre max --extra-index-url https://whl.modular.com/nightly/simple/ ``` For the stable release: ```python !pip install max ``` Wait for the "Successfully installed" message. 3. Enable Mojo: In the first cell, run: ```python import mojo.notebook ``` This adds the `%%mojo` cell magic, so you can compile and run Mojo code. Your Colab notebook is now ready to run Mojo programs. ## Using Mojo in Local Jupyter Notebooks Local notebooks use `pixi` to manage an environment with Jupyter and Mojo. 1. Create a project: ```shell pixi init notebooks \ -c https://conda.modular.com/max-nightly/ \ -c conda-forge cd notebooks pixi shell ``` This creates a project directory and enters the Pixi shell. 2. Install required tools: ```shell pixi add max jupyterlab ipykernel ``` This installs: - Mojo, by way of `max` — see [the note above](#using-mojo-on-google-colab) on why this page installs `max` rather than `mojo` - JupyterLab - The Python kernel required for notebook execution 3. Start JupyterLab: ```shell jupyter lab ``` JupyterLab opens in your browser. 4. Create a Python-backed notebook: In your web browser: - Select _File > New > Notebook_. - Choose the _Python_ kernel. 5. Enable Mojo support: In the first cell, run: ```python import mojo.notebook ``` This registers the `%%mojo` magic command. Your local environment is now ready for interactive Mojo development. ## Writing and running Mojo code Mojo code runs inside notebook cells marked with the `%%mojo` directive. Each Mojo cell must contain a complete program, including a `main()` function. ### Example: Hello Mojo ```mojo %%mojo def main(): print("Hello Mojo") ``` Output: ```output Hello Mojo ``` ### Example: Parameterized compilation ```mojo %%mojo # Compiler-parameterized function def repeat[count: Int](msg: String): comptime for i in range(count): print(msg) # Compiler-argumented function def threehello(): repeat[3]("Hello 🔥!") # Run def main(): threehello() ``` Output: ```output Hello 🔥! Hello 🔥! Hello 🔥! ``` ## Using Mojo with GPU support Google Colab offers GPU-backed runtimes so you can run Mojo GPU examples even without local hardware. The specific accelerator available depends on your Colab tier; see [GPU compatibility](/docs/requirements/#gpu-compatibility) for the list of accelerators supported by Mojo. Before running GPU code, select _Runtime > Change runtime type > [GPU]_. ### Example: GPU Hello World ```mojo %%mojo from max.gpu.host import DeviceContext def kernel(): print("Hello from the GPU") def main() raises: # Launch GPU kernel with DeviceContext() as ctx: ctx.enqueue_function[kernel](grid_dim=1, block_dim=1) ctx.synchronize() ``` Output: ```output Hello from the GPU ``` ### Example: Hello writing This example writes a value to device memory and reads it back on the host: ```mojo %%mojo from std.memory import Pointer from max.gpu.host import DeviceContext comptime `✅`: Int32 = 1 comptime `❌`: Int32 = 0 def kernel(value: Pointer[Scalar[.int32], MutAnyOrigin]): value[unsafe_offset=0] = `✅` def main() raises: with DeviceContext() as ctx: # Build it var out = ctx.enqueue_create_buffer[.int32](1) out.enqueue_fill(`❌`) # Run it ctx.enqueue_function[kernel](out, grid_dim=1, block_dim=1) # Report the result with out.map_to_host() as out_host: print("GPU responded:", \ "👋, 🔥" if out_host[0] == `✅` else "😢") ``` Output: ```output GPU responded: 👋, 🔥 ``` ### Example: GPU vector addition This example runs elementwise vector addition on the GPU. Each GPU thread updates one element. ```mojo %%mojo from max.gpu import thread_idx from max.gpu.host import DeviceContext from layout import TileTensor, row_major from std.sys import has_accelerator comptime VECTOR_WIDTH = 10 comptime layout = row_major[VECTOR_WIDTH]() comptime active_dtype = DType.uint8 # Elementwise vector addition on GPU threads def vector_addition( left: TileTensor[active_dtype, type_of(layout), MutAnyOrigin], right: TileTensor[active_dtype, type_of(layout), MutAnyOrigin], output: TileTensor[active_dtype, type_of(layout), MutAnyOrigin], ): var idx = thread_idx.x output[idx] = left[idx] + right[idx] def main() raises: # Ensure a supported GPU (NVIDIA or AMD) is available comptime assert has_accelerator(), "This example requires a supported GPU" # Create GPU device context var ctx = DeviceContext() # Allocate buffers and tensors for left and right operands, and output var left_buffer = ctx.enqueue_create_buffer[active_dtype](VECTOR_WIDTH) var left_tensor = TileTensor(left_buffer, layout) var right_buffer = ctx.enqueue_create_buffer[active_dtype](VECTOR_WIDTH) var right_tensor = TileTensor(right_buffer, layout) var output_buffer = ctx.enqueue_create_buffer[active_dtype](VECTOR_WIDTH) var output_tensor = TileTensor(output_buffer, layout) # Initialize input buffers with sample data var message_bytes: List[UInt8] = [ 71, 100, 107, 107, 110, 31, 76, 110, 105, 110 ] with left_buffer.map_to_host() as mapped_buffer: var mapped_tensor = TileTensor(mapped_buffer, layout) for idx in range(VECTOR_WIDTH): mapped_tensor[idx] = message_bytes[idx] _ = right_buffer.enqueue_fill(1) # Launch GPU kernel ctx.enqueue_function[vector_addition]( left_tensor, right_tensor, output_tensor, grid_dim=1, block_dim=VECTOR_WIDTH, ) ctx.synchronize() # Read results back and print as ASCII with output_buffer.map_to_host() as mapped_buffer: var mapped_tensor = TileTensor(mapped_buffer, layout) for idx in range(VECTOR_WIDTH): print(chr(Int(mapped_tensor[idx])), end="") print() ``` Output: ```output Hello Mojo ``` :::tip Learn Mojo GPU programming through the interactive [Mojo GPU Puzzles](https://puzzles.modular.com/introduction.html). ::: --- ## Packaging This page explains how to turn your Mojo project into a distributable conda package using rattler-build. You can distribute your conda package on any conda-compatible package index, such as [prefix.dev](https://prefix.dev), [anaconda.org](https://anaconda.org), or an S3 bucket. For the most visibility, we recommend sharing your package in the [modular-community channel](https://prefix.dev/channels/modular-community) on prefix.dev, as described below. ## How it works *rattler-build* is a tool that turns your source code into a conda package. You give it a *recipe*—a YAML file named `recipe.yaml`—and it does the rest: fetches your source, compiles it in an isolated environment, runs your tests, and writes out a `.conda` file ready to upload to a package index. The recipe is a declarative description of your package that specifies: - The source code location (a git commit or tarball URL) - The build process (a `mojo precompile` command) - Package dependencies - Test commands to verify the build The complete packaging process is: 1. Create a `recipe.yaml` that specifies your package details. 2. Run `rattler-build` to create a `.conda` package. 3. Share the package in a public package index. :::note If you want to distribute your package in the modular-community channel, you only need to merge your `recipe.yaml` file into the [modular-community repository](https://github.com/modular/modular-community) (the repo handles steps 2 and 3). ::: Then you and other users can install your package with `pixi` or other conda package managers by adding the appropriate conda channel to your project manifest file (`pixi.toml`). ## Install rattler-build We recommend using [Pixi](https://pixi.sh/latest/) to install `rattler-build`: 1. If you don't have it, install `pixi` with this command: ```bash curl -fsSL https://pixi.sh/install.sh | sh ``` Then restart your terminal for the changes to take effect. 2. Now install `rattler-build` globally: ```bash pixi global install rattler-build ``` 3. Verify the installation: ```bash rattler-build --version ``` ## Write your recipe file The recipe is the heart of the packaging process and is defined in a YAML file named `recipe.yaml`. By convention, store your recipe in your project root at `conda.recipe/recipe.yaml`. `rattler-build` looks there by default, and it's the location expected by the GitHub Action ([rattler-build-action](https://github.com/prefix-dev/rattler-build-action)). For example: ```output my-mojo-lib/ ├── src/ │ └── my_mojo_lib/ │ ├── __init__.mojo │ └── utils.mojo ├── test.mojo ├── conda.recipe/ │ └── recipe.yaml ├── LICENSE └── README.md ``` ### The minimal recipe file This section covers the most important recipe fields for Mojo packages. For details about all available recipe fields, see the [rattler-build recipe reference](https://rattler-build.prefix.dev/latest/reference/recipe_file/). You can copy this template to begin building your `recipe.yaml` file: ```yaml title="recipe.yaml" context: version: "0.1.0" package: name: my-mojo-lib version: ${{ version }} source: - git: https://github.com/yourname/my-mojo-lib.git rev: a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2 build: number: 0 script: - mojo precompile src/my_mojo_lib -o ${{ PREFIX }}/lib/mojo/my_mojo_lib.mojoc requirements: build: - mojo-compiler =25.5.0 host: - mojo-compiler =25.5.0 run: - ${{ pin_compatible('mojo-compiler') }} tests: - script: - if: unix then: - mojo run test.mojo files: recipe: - test.mojo about: homepage: https://github.com/yourname/my-mojo-lib repository: https://github.com/yourname/my-mojo-lib license: MIT license_file: LICENSE summary: A short one-line description of what your library does. extra: maintainers: - yourname ``` ### Recipe tips Here are a few things that are particularly important for Mojo packages. #### Use a full commit SHA as the source revision The `source.rev` field should be a full 40-character git commit SHA rather than a branch name or tag. This makes the build reproducible—anyone who builds from the same recipe gets the exact same source code. #### Set the build number Start `build.number` at `0`. If you need to rebuild the same version of your library (such as to pick up a new Mojo compiler release), increment `build.number` rather than changing the version. Reset it to `0` when you bump the version. #### Specify the install location In the above recipe, look at the `mojo precompile` command in the `build.script` section. It's important that this command outputs the `.mojoc` file into `$PREFIX/lib/mojo/`, because this path is what makes the package auto-discoverable by the Mojo compiler. When `rattler-build` runs your build script, it sets a `$PREFIX` environment variable pointing to the root of an isolated installation directory. Any files your script places under `$PREFIX` become part of the conda package—a file written to `$PREFIX/lib/mojo/foo.mojoc` during the build is extracted into your actual environment when you run `pixi add foo`. Think of `$PREFIX` as a stand-in for wherever your environment lives on your machine. #### Pin the Mojo compiler version Precompiled Mojo files compile against a specific compiler version and might not be compatible with other versions. The required `mojo-compiler` version must be specified in the `requirements.build` section of your recipe, which must conform to the [conda package match syntax](https://docs.conda.io/projects/conda/en/latest/user-guide/concepts/pkg-specs.html#package-match-specifications). The `pin_compatible('mojo-compiler')` function in `requirements.run` generates a version constraint based on whichever version is resolved at build time, preventing your package from silently running against an incompatible runtime. ## Build the package With your recipe file in hand, you can build the package using `rattler-build` from your project root: ```bash rattler-build build \ --recipe conda.recipe/recipe.yaml \ -c conda-forge \ -c https://conda.modular.com/max \ -c https://repo.prefix.dev/modular-community ``` The `-c` flags specify which conda channels to search for dependencies (in priority order). You need: - `conda-forge` for general tooling - `https://conda.modular.com/max` for `mojo-compiler` and `max` - `https://repo.prefix.dev/modular-community` if you depend on other community Mojo packages When you run `rattler-build build`, it: 1. Creates an isolated build environment 2. Fetches your source code 3. Runs your build script to compile the `.mojoc` file 4. Bundles the result into a `.conda` archive 5. Runs your test commands to verify the package works The output file appears in an `output/` directory, for example: ```text output/ └── linux-64/ └── my-mojo-lib-0.3.0-h1a2b3c4_0.conda ``` The hash in the filename (`h1a2b3c4`) is derived from the build configuration and is managed automatically by rattler-build. ## Debug a failed build If the build fails, open a debug shell to investigate interactively: ```bash rattler-build debug shell ``` This gives you a shell with all environment variables set (`$PREFIX`, `$SRC_DIR`, etc.) and the build environment activated, so you can run your build commands to find the problem. For more details, see the [rattler-build debugging guide](https://rattler-build.prefix.dev/latest/debugging_builds/). ## Publish to a package index Once you have a built `.conda` file, you can upload it to any compatible host, such as [prefix.dev](https://prefix.dev), [anaconda.org](https://anaconda.org), or an AWS S3 bucket. For the most visibility, add your package to the [modular-community channel](https://prefix.dev/channels/modular-community) (hosted on prefix.dev), as described below. ### Publish to the modular-community channel To publish your package on the [modular-community channel](https://prefix.dev/channels/modular-community), open a pull request to the [modular-community GitHub repo](https://github.com/modular/modular-community) to add your package's `recipe.yaml` file. The repo automatically builds and hosts all the packages based on the recipes in the repo. Your `recipe.yaml` is the same file described above. Just add it to a new directory that matches your package name: ```text modular-community/ └── recipes/ └── my-mojo-lib/ └── recipe.yaml ``` Once published to the channel, you can install your package with `pixi` by adding the `https://repo.prefix.dev/modular-community` channel to your project manifest: ```toml title="pixi.toml" [workspace] channels = [ "https://conda.modular.com/max-nightly", "https://repo.prefix.dev/modular-community", "conda-forge", ] ``` :::note If your package includes Python or any language other than Mojo, you must enable [CodeQL scanning](https://docs.github.com/en/code-security/code-scanning/enabling-code-scanning/configuring-default-setup-for-code-scanning) on your source repository and add the badge to your README. ::: For more details, see the [modular-community README](https://github.com/modular/modular-community?tab=readme-ov-file#modular-community-channel). ### Update your package When you release a new version of your library: 1. Update `context.version` in your recipe. 2. Update `source.rev` to the new commit SHA (or update the tarball URL and SHA256). 3. Reset `build.number` to `0`. 4. Open a new PR to modular-community (if you've already published it there). If you're republishing the same version (for example, to support a new Mojo compiler release), increment `build.number` instead of changing the version. ## Useful links - [modular-community repository](https://github.com/modular/modular-community) - [rattler-build recipe reference](https://rattler-build.prefix.dev/latest/reference/recipe_file/) --- ## Mojo AI skills Mojo is ideal for agentic programming because it has a concise language syntax and your agent will catch most of the coding errors at compile time. To make your token usage even more efficient, our Mojo skills ensure that you generate up-to-date and idiomatic Mojo code from the start. Many AI models are trained on older versions of Mojo and MAX. They aren't updated as quickly as the language evolves, so they often generate code that doesn't compile or reflects outdated usage. For best results, agents need accurate, up-to-date context. Mojo agent skills are designed to be compact and focused, providing only the most important guidance needed to avoid common code generation issues. This keeps token usage low and leaves more room for relevant context. [Modular skills](https://github.com/modular/skills/tree/main) provide current guidance on Mojo syntax, development patterns, and workflows so AI coding agents generate modern, working code that aligns with the language today. ## What you can do with this - Start new Mojo or MAX projects without manual setup ("I want to start a new Mojo project for image enhancement") - Generate modern Mojo syntax ("Write a function that applies a transformation around the center") - Write GPU code using valid patterns ("Convert this CPU function to run on GPU") - Use Python interoperability correctly ("Update this Mojo code to use NumPy") - Port code from CUDA, Python, or C++ ("Convert this CUDA function to Mojo") You describe the goal. Your system handles the language. ## Installation **Install all skills**: ```text npx skills add modular/skills ``` **Install a specific skill**: ```text npx skills add modular/skills --skill mojo-syntax ``` **Update skills**: ```text npx skills update ``` ### Manual installation **HTTPS:** ```text git clone https://github.com/modular/skills.git ``` **SSH:** ```text git clone git@github.com:modular/skills.git ``` **CLI:** ```text gh repo clone modular/skills ``` ### Configuration Copy or symlink individual skill files into your agent's configuration directory. ## How it works Skills follow the [Agent Skills Standard](https://agentskills.io/specification). Each skill is self-contained, triggered by intent, and structured for reliable use by AI agents. At runtime: 1. The agent interprets your request. 2. It selects the right skill (for example, `mojo-syntax` or `mojo-gpu-fundamentals`). 3. The skill guides generation toward current Mojo and MAX patterns. This isn't prompting. It's controlled code generation. ## Connect to the docs MCP server Connect the docs [Model Context Protocol](https://modelcontextprotocol.io) (MCP) server to give your AI assistant live access to Mojo's documentation. Your assistant can search Mojo's manual, API references, and code examples while it plans, writes, and debugs your code, so its answers stay grounded in the current documentation instead of its training data. Your assistant can usually set up the MCP server itself with a prompt like this: ```text Add the Mojo docs MCP server at https://mojo-mcp.modular.com/mcp/ and verify it by searching the docs. ``` Or configure it manually in your tool's settings. With Claude Code, add the server from the command line: ```sh claude mcp add --transport http mojo-docs https://mojo-mcp.modular.com/mcp/ ``` With Cursor, add the server to `~/.cursor/mcp.json`: ```json { "mcpServers": { "mojo-docs": { "url": "https://mojo-mcp.modular.com/mcp/" } } } ``` Any other client that supports MCP's streamable HTTP transport connects with the same URL. The server indexes both the stable and nightly documentation. ## FAQ **Are the skills always up to date with the latest Mojo?** Skills are updated regularly to track changes in Mojo and MAX, but there will be lag between language changes and skill updates. **Can I select a skill version that matches my installed Mojo version?** Not currently. Skills aren't versioned by Mojo release, so there may be mismatches between the skill's guidance and your installed version. We recommend installing the latest version of Mojo to minimize this risk. **Do I have to install all the skills?** No. Install only what you need. **Are the skills licensed?** Yes. They're available under the Apache 2.0 license. --- ## Testing Mojo includes a framework for developing and executing unit tests. The Mojo testing framework consists of a set of assertions defined as part of the [Mojo standard library](/docs/std) and the [`TestSuite`](/docs/std/testing/suite/TestSuite/) struct for automatic test discovery and execution. ## Get started Let's start with a simple example of writing and running Mojo tests. ### 1. Write tests For your first example of using the Mojo testing framework, create a file named `test_quickstart.mojo` containing the following code: ```mojo # Content of test_quickstart.mojo from std.testing import assert_equal, TestSuite def inc(n: Int) -> Int: return n + 1 def test_inc_zero() raises: # This test contains an intentional logical error to show an example of # what a test failure looks like at runtime. assert_equal(inc(0), 0) def test_inc_one() raises: assert_equal(inc(1), 2) def main() raises: TestSuite.discover_tests[__functions_in_module()]().run() ``` In this file, the `inc()` function is the test *target*. The functions whose names begin with `test_` are the tests. Usually you should define the target in a separate source file from its tests, but you can define them in the same file for this simple example. A test function *fails* if it raises an error when executed, otherwise it *passes*. The two tests in this example use the `assert_equal()` function, which raises an error if the two values provided are not equal. :::note The implementation of `test_inc_zero()` contains an intentional logical error so that you can see an example of a failed test when you execute it in the next step of this tutorial. ::: ### 2. Execute tests Then in the directory containing the file, execute the following command in your shell: ```bash mojo run test_quickstart.mojo ``` You should see output similar to this (note that this example elides the full filesystem paths from the output shown): ```output Unhandled exception caught during execution: Running 2 tests for ROOT_DIR/test_quickstart.mojo FAIL [ 0.009 ] test_inc_zero At ROOT_DIR/test_quickstart.mojo:40:5: AssertionError: `left == right` comparison failed: left: 1 right: 0 PASS [ 0.001 ] test_inc_one -------- Summary [ 0.009 ] 2 tests run: 1 passed , 1 failed , 0 skipped Test suite 'ROOT_DIR/test_quickstart.mojo' failed! mojo: error: execution exited with a non-zero result: 1 ``` The output shows each test as it runs with PASS or FAIL status and execution time, followed by a summary of tests run, passed, failed, and skipped. Failed tests display their error messages inline. ### Next steps - [Using Mojo assertion functions](#using-mojo-assertion-functions) describes the assertion functions available to help implement tests. - [Writing unit tests](#writing-unit-tests) shows how to write unit tests and organize them into test files. - Our GitHub repo contains an [example project](https://github.com/modular/modular/tree/mojo/v1.1.0/Mojo/examples/testing) to demonstrate unit testing. Several of the examples shown later are based on this project. ## Using Mojo assertion functions The Mojo standard library includes a [`testing`](/docs/std/testing/testing/) module that defines several assertion functions for implementing tests. Each assertion returns `None` if its condition is met or raises an error if it isn't. - [`assert_true()`](/docs/std/testing/testing/assert_true/): Asserts that the input value is `True`. - [`assert_false()`](/docs/std/testing/testing/assert_false/): Asserts that the input value is `False`. - [`assert_equal()`](/docs/std/testing/testing/assert_equal/): Asserts that the input values are equal. - [`assert_not_equal()`](/docs/std/testing/testing/assert_not_equal/): Asserts that the input values are not equal. - [`assert_almost_equal()`](/docs/std/testing/testing/assert_almost_equal/): Asserts that the input values are equal up to a tolerance. The boolean assertions report a basic error message when they fail. ```mojo from std.testing import * assert_true(False) ``` ```output Unhandled exception caught during execution Error: At Expression [1] wrapper:14:16: AssertionError: condition was unexpectedly False ``` Each function also accepts an optional `msg` keyword argument for providing a custom message to include if the assertion fails. ```mojo assert_true(False, msg="paradoxes are not allowed") ``` ```output Unhandled exception caught during execution Error: At Expression [2] wrapper:14:16: AssertionError: paradoxes are not allowed ``` For comparing floating-point values, you should use `assert_almost_equal()`, which allows you to specify either an absolute or relative tolerance. ```mojo var result = 10 / 3 assert_almost_equal(result, 3.33, atol=0.001, msg="close but no cigar") ``` ```output Unhandled exception caught during execution Error: At Expression [3] wrapper:15:24: AssertionError: 3.3333333333333335 is not close to 3.3300000000000001 with a diff of 0.0033333333333334103 (close but no cigar) ``` The testing module also defines a [context manager](/docs/manual/errors#use-a-context-manager), [`assert_raises()`](/docs/std/testing/testing/assert_raises/), to assert that a given code block correctly raises an expected error. ```mojo def inc(n: Int) raises -> Int: if n == Int.MAX: raise Error("inc overflow") return n + 1 print("Test passes because the error is raised") with assert_raises(): _ = inc(Int.MAX) print("Test fails because the error isn't raised") with assert_raises(): _ = inc(Int.MIN) ``` ```output Unhandled exception caught during execution Test passes because the error is raised Test fails because the error isn't raised Error: AssertionError: Didn't raise at Expression [4] wrapper:18:23 ``` :::note The example above assigns the return value from `inc()` to a [*discard pattern*](/docs/manual/lifecycle/death/#explicit-lifetime-extension). Without it, the Mojo compiler reports a warning that the return value is unused. ::: You can also provide an optional `contains` argument to `assert_raises()` to indicate that the test passes only if the error message contains the substring specified. Other errors are propagated, failing the test. ```mojo print("Test passes because the error contains the substring") with assert_raises(contains="required"): raise Error("missing required argument") print("Test fails because the error doesn't contain the substring") with assert_raises(contains="required"): raise Error("invalid value") ``` ```output Unhandled exception caught during execution Test passes because the error contains the substring Test fails because the error doesn't contain the substring Error: invalid value ``` ## Writing unit tests A Mojo unit test is simply a function that fulfills all of these requirements: - Has a name that starts with `test_` for automatic discovery. - Accepts no arguments. - Returns `None`. - Raises an error to indicate test failure. - Is defined at the module scope, not as a Mojo struct method. Generally, you should use the assertion utilities from the Mojo standard library [`testing`](/docs/std/testing/testing/) module to implement your tests. You can include multiple related assertions in the same test function. However, if an assertion raises an error during execution, then the test function returns immediately, skipping any subsequent assertions. ## Running tests with TestSuite To run your tests, each test file must include a `main()` function that uses [`TestSuite.discover_tests()`](/docs/std/testing/suite/TestSuite/#discover_tests) to automatically discover and execute all test functions in the module. The `__functions_in_module()` compiler intrinsic provides a list of all functions defined in the current module, which `discover_tests()` filters to find those with the `test_` prefix. Here is an example of a test file containing three tests for functions defined in a source module named `my_target_module` (which is not shown here). ```mojo # File: test_my_target_module.mojo from my_target_module import convert_input, validate_input from std.testing import assert_equal, assert_false, assert_raises, assert_true, TestSuite def test_validate_input() raises: assert_true(validate_input("good"), msg="'good' should be valid input") assert_false(validate_input("bad"), msg="'bad' should be invalid input") def test_convert_input() raises: assert_equal(convert_input("input1"), "output1") assert_equal(convert_input("input2"), "output2") def test_convert_input_error() raises: with assert_raises(): _ = convert_input("garbage") def main() raises: TestSuite.discover_tests[__functions_in_module()]().run() ``` You can then use `mojo run test_my_target_module.mojo` to run the tests and report the results. ## Filtering tests By default, a `TestSuite` runs every test discovered in a test file. You can filter which tests run, either from the command line or programmatically. This is useful when you want to focus on a single failing test, exclude a known-broken or flaky test, or list the tests in a file without running them. The examples in this section use the test files from the [example project](https://github.com/modular/modular/tree/mojo/v1.1.0/Mojo/examples/testing), which defines the tests `test_inc_valid()` and `test_inc_max()` in `test/my_math/test_inc.mojo`. ### Filter tests from the command line A test file that uses `TestSuite` accepts the following command line flags: - `--skip `: Run all tests *except* the named ones (a skip-list). - `--only `: Run *only* the named tests (an allow-list). - `--skip-all`: Skip every test, collecting and listing the tests without running any of them. For example, to run every test in `test_inc.mojo` except `test_inc_max()`: ```bash mojo run -I src test/my_math/test_inc.mojo --skip test_inc_max ``` Skipped tests appear in the output with `SKIP` status: ```output Running 2 tests for test/my_math/test_inc.mojo PASS [ 0.001 ] test_inc_valid SKIP [ 0.001 ] test_inc_max -------- Summary [ 0.001 ] 2 tests run: 1 passed , 0 failed , 1 skipped ``` To run only `test_inc_valid()`, use the `--only` flag: ```bash mojo run -I src test/my_math/test_inc.mojo --only test_inc_valid ``` To collect and list the tests without running any of them, use `--skip-all`. This is handy when you want to see which tests a file contains: ```bash mojo run -I src test/my_math/test_inc.mojo --skip-all ``` A few things to note about these flags: - The `--skip` and `--only` flags each accept multiple test names as separate, space-separated arguments. For example, `--skip test_inc_valid test_inc_max` skips both tests. Don't combine the names into a single quoted argument. - Test names must match exactly. If you pass a name that doesn't correspond to a discovered test, the suite raises an error and exits with a non-zero status. - You can use only one of these flags per command. The `--skip-all` flag takes no test names. ### Skip tests programmatically Instead of, or in addition to, filtering from the command line, you can skip specific tests in the test file itself. Capture the suite returned by `discover_tests()` in a variable and call [`skip()`](/docs/std/testing/suite/TestSuite/#skip) before running the suite: ```mojo def main() raises: var suite = TestSuite.discover_tests[__functions_in_module()]() suite.skip[test_inc_max]() suite^.run() ``` Note the `^` transfer sigil in the call to [`run()`](/docs/std/testing/suite/TestSuite/#run): the method consumes the suite, so you must transfer ownership of the suite to it. To skip more than one test, call `skip()` once for each test you want to skip: ```mojo def main() raises: var suite = TestSuite.discover_tests[__functions_in_module()]() suite.skip[test_broken]() suite.skip[test_flaky]() suite^.run() ``` A programmatic skip always takes effect, even when you use `--only` to allow a test that's also skipped in the code. This makes `skip()` a good fit for tests that you want to keep disabled regardless of the command line filters, such as a test that's broken, flaky, or that depends on an unavailable environment. For more information, see the [`TestSuite`](/docs/std/testing/suite/TestSuite/) API reference. --- ## mojo build Builds an executable from a Mojo file. ## Synopsis ``` mojo build [options] ``` ## Description Compiles the Mojo file at the given path into an executable. By default, the executable is saved to the current directory and named the same as the input file, but without a file extension. Beware that any Python libraries used in your Mojo project are not included in the executable binary, so they must be provided by the environment where you run the executable. ## Options ### Output options #### `-o ` Sets the path and filename for the executable output. By default, it outputs the executable to the current directory, with the same name and no extension. #### `--emit ` The type of output file to generate. * `exe` (default): emit an executable binary file. * `shared-lib`: emit a shared (dynamic) library. * `object`: (EXPERIMENTAL) emit a single object file. * `llvm`: emit unoptimized LLVM IR. * `llvm-bitcode`: emit bitcode of unoptimized LLVM IR. * `asm`: emit target assembly. For GPU targets, also emits a sidecar file per kernel alongside the host assembly: `.ptx` for NVIDIA, `.amdgcn` for AMD, `.ll` for Metal. ### Compilation options #### `--optimization-level `, `-O`, `--no-optimization (LEVEL=0)` Sets the level of optimization to use at compilation. The value must be a number between 0 and 3. The default is 3. #### `-I ` Appends the given path to the list of directories to search for imported Mojo files. #### `-D ` Defines a named value that can be used from within the Mojo source file being executed. For example, `-Dfoo=42` defines a name `foo` that, when queried with the `std.defines` module from within the Mojo program, would yield the compile-time value `42`. #### `--debug-level `, `-g (LEVEL=full)`, `-g0 (LEVEL=none)`, `-g1 (LEVEL=line-tables)`, `-g2 (LEVEL=full)` Sets the level of debug info to use at compilation. The value must be one of: `none`, `line-tables`, or `full`. Default is `none`, except when using `mojo debug foo.mojo`, which defaults to `full`. Please note that there are issues when generating debug info for some Mojo programs that have yet to be addressed. #### `--num-threads `, `-j` Sets the maximum number of threads to use for compilation. The default is 0 (use all available threads). #### `--elaboration-error-include-prelude` Show elaboration error with locations in mojo startup modules (prelude). #### `--fp-mode ` Controls floating-point behavior as a comma-separated list of `feature=value` items (may be given more than once). The only feature is `contract`, one of `fast` (default) or `off`. `contract=fast` is like Clang's `-ffp-contract=fast`: it fuses `a + b*c` into an FMA across statements and breaking strict IEEE compliance. `contract=off` disables contraction. ### Target options #### `--target-triple ` Sets the compilation target triple. Defaults to the host target. #### `--target-cpu ` Sets the compilation target CPU. Defaults to the host CPU. #### `--target-features ` Sets the compilation target CPU features. Defaults to the host features. #### `--target-abi ` Sets the target ABI name (e.g. `lp64d`), recorded as a `target-abi` LLVM module flag. Unset by default. #### `--march ` Sets the architecture for which to generate code. #### `--mcpu ` Sets the CPU for which to generate code. #### `--mtune ` Sets the CPU for which to tune code. #### `--target-accelerator ` Sets the GPU or accelerator architecture for heterogeneous computing (e.g., sm_90 for NVIDIA H100, gfx942 for AMD MI300). #### `--print-effective-target` Print the effective target configuration after absorbing all command-line flags and exit. #### `--print-supported-targets` Print all available target names and exit. #### `--print-supported-cpus` Print valid CPU names for the specified target and exit. Requires --target-triple. #### `--print-supported-accelerators` Print all supported GPU and accelerator architectures and exit. ### Compilation diagnostic options Controls how the Mojo compiler outputs diagnostics related to compiling and running Mojo source code. #### `--diagnose-missing-doc-strings` Emits diagnostics for missing or partial doc strings. #### `--max-notes-per-diagnostic ` When the Mojo compiler emits diagnostics, it sometimes also prints notes with additional information. This option sets an upper threshold on the number of notes that can be printed with a diagnostic. If not specified, the default maximum is 10. #### `--disable-builtins` Do not use builtins when create package. #### `--disable-warnings` Do not print warning messages. #### `--experimental-fixit` Automatically apply fix-its to the code, and rerun the command again after the fix-its are applied. WARNING: this feature is highly experimental and may result in irreversible data loss. #### `--experimental-export-fixit ` Export fix-its to a YAML file in clang-tidy format instead of applying them directly. The file can be applied using 'clang-apply-replacements'. WARNING: this feature is highly experimental. #### `--Werror` Treat warnings as errors. #### `--Wno-error` Do not treat warnings as errors. #### `--warn-on-unstable-apis` Warn when using unstable APIs from the standard library. #### `--ignore-incompatible-precompiled-file-errors` Ignore errors encountered when loading incompatible Mojo precompiled files. #### `--ignore-deprecated ` Suppress the deprecation warning for the given declaration (e.g. `Foo.bar`, or `some_fn` for a top-level declaration). ### Linker options #### `-Xlinker ` Pass ARG to the linker. #### `--lld-path ` Overrides the path to the `lld` linker used when linking. Takes precedence over the `MODULAR_MOJO_MAX_LLD_PATH` environment variable and the `mojo-max.lld_path` configuration value. ### Experimental compilation options #### `--sanitize ` Turns on runtime checks. The following values are supported: `address` (detects memory issues), and `thread` (detects multi-threading issues). #### `--shared-libasan` Dynamically link the address sanitizer runtime. Requires address sanitization turned on with `--sanitize` option. #### `--debug-info-language ` Sets the language to emit as part of the debug info. The supported languages are: `Mojo`, and `C`. `Mojo` is the default. `C` is useful to enable rudimentary debugging and binary introspection in tools that don't understand Mojo, but is not required for `mojo debug`. ### Common options #### `--diagnostic-format ` The format in which diagnostics and error messages are printed. Must be one of "text" or "json" ("text" is the default). #### `--help`, `-h` Displays help information. #### `--help-hidden` Displays help for hidden options. --- ## mojo debug Launches the Mojo debugger using the command-line interface or an external editor. ## Synopsis ``` mojo debug [debug-options] ``` ## Description This command, which underneath uses the LLDB debugger, or cuda-gdb, offers four basic debug session modes: * Build and debug a Mojo file. mojo debug [options] [runtime args] Builds the Mojo file at the given path and launches it under the debugger. Options, which come before the Mojo file, can include any compilation options expected by the `mojo run`, as well as regular debuggingcommands. Runtime args, which come after the Mojo file, are passed directly to the debuggee upon launch. By default, this mode uses `-O0` and `--debug-level=full` as compilation options. * Debug a precompiled program. mojo debug [options] [runtime args] Launches the program at the given path in the debugger. Options, which come before the program path, cannot include compilation commands. Runtime args, which come after the program path, are passed directly to the debuggee upon launch. * Attach to a running process. mojo debug [options] [--pid | --process-name ] Attaches to the process specified by pid or name, which can be the full path of the process' executable. Options other than the process identifier cannot include compilation options. * Start the debugger command-line interface. mojo debug [options] Launches the debugger CLI with support for debugging Mojo programs. This command only supports LLDB or cuda-gdb options via the `--X` option. You can also select one of two interfaces for the debug session: * CLI: By default, all debug session modes are launched using the regular debugger command-line interface. * VS Code Debug Server: If you add the `--vscode` option, the debug session is launched in VS Code via the Mojo extension. VS Code must be running and the Mojo extension must be enabled. Besides that, the environment variables and the current working directory of this invocation are preserved when launching programs in the debugger on VS Code. Finally, it is worth mentioning that this debugger can debug programs written in other standard native languages like Rust, C and C++, as it is based on LLDB or cuda-gdb. Debugger capabilities: * LLDB: this is the default debugger and has great support for CPU Mojo code, but has no support at all for Mojo GPU code. * cuda-gdb: this is invoked via the `--cuda-gdb` option and has minimal support for CPU Mojo code but it has support for GPU Mojo code. ## Options ### Attach options #### `--pid ` Indicates the debugger to attach to the process with the given PID. #### `--process-name ` Indicates the debugger to attach to the process with the given name or path. ### cuda-gdb options #### `--cuda-gdb` Uses cuda-gdb instead of LLDB for debugging. In this mode, it's possible to step into GPU code, but the CPU debugging experience is degraded. #### `--cuda-gdb-path ` Uses the given CUDA_GDB_PATH instead of looking for cuda-gdb in the PATH environment variable. #### `--break-on-launch` Set the breakOnLaunch option for cuda-gdb. This makes the debugger break on the first instruction of every launched kernel. ### Compilation options #### `--optimization-level `, `-O`, `--no-optimization (LEVEL=0)` Sets the level of optimization to use at compilation. The value must be a number between 0 and 3. The default is 3. #### `-I ` Appends the given path to the list of directories to search for imported Mojo files. #### `-D ` Defines a named value that can be used from within the Mojo source file being executed. For example, `-Dfoo=42` defines a name `foo` that, when queried with the `std.defines` module from within the Mojo program, would yield the compile-time value `42`. #### `--debug-level `, `-g (LEVEL=full)`, `-g0 (LEVEL=none)`, `-g1 (LEVEL=line-tables)`, `-g2 (LEVEL=full)` Sets the level of debug info to use at compilation. The value must be one of: `none`, `line-tables`, or `full`. Default is `none`, except when using `mojo debug foo.mojo`, which defaults to `full`. Please note that there are issues when generating debug info for some Mojo programs that have yet to be addressed. #### `--num-threads `, `-j` Sets the maximum number of threads to use for compilation. The default is 0 (use all available threads). #### `--elaboration-error-include-prelude` Show elaboration error with locations in mojo startup modules (prelude). #### `--fp-mode ` Controls floating-point behavior as a comma-separated list of `feature=value` items (may be given more than once). The only feature is `contract`, one of `fast` (default) or `off`. `contract=fast` is like Clang's `-ffp-contract=fast`: it fuses `a + b*c` into an FMA across statements and breaking strict IEEE compliance. `contract=off` disables contraction. ### Target options #### `--target-triple ` Sets the compilation target triple. Defaults to the host target. #### `--target-cpu ` Sets the compilation target CPU. Defaults to the host CPU. #### `--target-features ` Sets the compilation target CPU features. Defaults to the host features. #### `--target-abi ` Sets the target ABI name (e.g. `lp64d`), recorded as a `target-abi` LLVM module flag. Unset by default. #### `--march ` Sets the architecture for which to generate code. #### `--mcpu ` Sets the CPU for which to generate code. #### `--mtune ` Sets the CPU for which to tune code. #### `--target-accelerator ` Sets the GPU or accelerator architecture for heterogeneous computing (e.g., sm_90 for NVIDIA H100, gfx942 for AMD MI300). #### `--print-effective-target` Print the effective target configuration after absorbing all command-line flags and exit. #### `--print-supported-targets` Print all available target names and exit. #### `--print-supported-cpus` Print valid CPU names for the specified target and exit. Requires --target-triple. #### `--print-supported-accelerators` Print all supported GPU and accelerator architectures and exit. ### Compilation diagnostic options Controls how the Mojo compiler outputs diagnostics related to compiling and running Mojo source code. #### `--diagnose-missing-doc-strings` Emits diagnostics for missing or partial doc strings. #### `--max-notes-per-diagnostic ` When the Mojo compiler emits diagnostics, it sometimes also prints notes with additional information. This option sets an upper threshold on the number of notes that can be printed with a diagnostic. If not specified, the default maximum is 10. #### `--disable-builtins` Do not use builtins when create package. #### `--disable-warnings` Do not print warning messages. #### `--experimental-fixit` Automatically apply fix-its to the code, and rerun the command again after the fix-its are applied. WARNING: this feature is highly experimental and may result in irreversible data loss. #### `--experimental-export-fixit ` Export fix-its to a YAML file in clang-tidy format instead of applying them directly. The file can be applied using 'clang-apply-replacements'. WARNING: this feature is highly experimental. #### `--Werror` Treat warnings as errors. #### `--Wno-error` Do not treat warnings as errors. #### `--warn-on-unstable-apis` Warn when using unstable APIs from the standard library. #### `--ignore-incompatible-precompiled-file-errors` Ignore errors encountered when loading incompatible Mojo precompiled files. #### `--ignore-deprecated ` Suppress the deprecation warning for the given declaration (e.g. `Foo.bar`, or `some_fn` for a top-level declaration). ### Debugger options #### `--X ` Passes ARG as an argument to the debugger when the debug session is launched using the debugger command-line interface. This option can be specified multiple times. It is ignored when using the RPC mode. ### Debug server options #### `--vscode` Launches the debug session on VS Code via the Mojo extension. #### `--rpc` Alias for --vscode. #### `--terminal ` The type of terminal to use when starting a launch debug session. * `console` (default): the debuggee will be launched in the default environment for the editor. If using VS Code, this will be the Debug Console. * `dedicated`: the debuggee will be launched in a dedicated terminal within the editor. #### `--port ` Uses the given PORT to communicate with the RPC debug server. Defaults to trying all ports from 12355 to 12364 inclusive. #### `--stop-on-entry` Automatically stop after launch. #### `--init-command ` Initialization command executed upon debugger startup. Can be specified multiple times. ### Linker options #### `-Xlinker ` Pass ARG to the linker. #### `--lld-path ` Overrides the path to the `lld` linker used when linking. Takes precedence over the `MODULAR_MOJO_MAX_LLD_PATH` environment variable and the `mojo-max.lld_path` configuration value. ### Experimental compilation options #### `--sanitize ` Turns on runtime checks. The following values are supported: `address` (detects memory issues), and `thread` (detects multi-threading issues). #### `--shared-libasan` Dynamically link the address sanitizer runtime. Requires address sanitization turned on with `--sanitize` option. #### `--debug-info-language ` Sets the language to emit as part of the debug info. The supported languages are: `Mojo`, and `C`. `Mojo` is the default. `C` is useful to enable rudimentary debugging and binary introspection in tools that don't understand Mojo, but is not required for `mojo debug`. ### Common options #### `--help`, `-h` Displays help information. #### `--help-hidden` Displays help for hidden options. --- ## mojo demangle Demangles the given name. ## Synopsis ``` mojo demangle [options] ``` ## Description If the given name is a mangled Mojo symbol name, prints the demangled name. If no name is provided, one is read from standard input. ## Options ### Common options #### `--help`, `-h` Displays help information. #### `--help-hidden` Displays help for hidden options. --- ## mojo doc Compiles docstrings from a Mojo file. ## Synopsis ``` mojo doc [options] ``` ## Description This is an early version of a documentation tool that generates an API reference from Mojo code comments. Currently, it generates a structured output of all docstrings into a JSON file, and it does not generate HTML. This output format is subject to change. The input may be a single file or a directory. If you specify a directory, it will generate a single JSON output with documentation for all modules found in that path, recursively. ## Options ### Output options #### `-o ` Sets the path and filename for the JSON output. If not provided, output is written to stdout. ### Compilation options #### `-I ` Appends the given path to the list of directories that Mojo will search for any package/module dependencies. That is, if the file you pass to `mojo doc` imports any packages that do not reside in the local path and are not part of the Mojo standard library, use this to specify the path where Mojo can find those packages. ### Validation options The following validation options help ensure that your docstrings use valid structure and meet other style criteria. By default, warnings are emitted only if the docstrings contain errors that prevent translation to the output format. (More options coming later.) #### `--diagnose-missing-doc-strings` Emits diagnostic warnings for missing or partial doc strings. #### `--docs-base-path ` Sets the path prefix for generated documentation links. ### Compilation diagnostic options Controls how the Mojo compiler outputs diagnostics related to compiling and running Mojo source code. #### `--max-notes-per-diagnostic ` When the Mojo compiler emits diagnostics, it sometimes also prints notes with additional information. This option sets an upper threshold on the number of notes that can be printed with a diagnostic. If not specified, the default maximum is 10. #### `--Werror` Treat warnings as errors. #### `--Wno-error` Do not treat warnings as errors (overrides -Werror). #### `--ignore-deprecated ` Suppress the deprecation warning for the given declaration (e.g. `Foo.bar`, or `some_fn` for a top-level declaration). ### Common options #### `--diagnostic-format ` The format in which diagnostics and error messages are printed. Must be one of "text" or "json" ("text" is the default). #### `--help`, `-h` Displays help information. #### `--help-hidden` Displays help for hidden options. --- ## mojo format Formats Mojo source files. ## Synopsis ``` mojo format [options] ``` ## Description Formats the given set of Mojo sources using a Mojo-specific lint tool. ## Options ### Format options #### `--line-length `, `-l ` Sets the max character line length. Default is 80. ### Diagnostic options #### `--quiet`, `-q` Disables non-error messages. ### Common options #### `--help`, `-h` Displays help information. #### `--help-hidden` Displays help for hidden options. --- ## mojo The Mojo🔥 command line interface. ## Synopsis ``` mojo mojo [run-options] mojo [options] mojo ``` ## Description The `mojo` CLI provides all the tools you need for Mojo development, such as commands to run, compile, and precompile Mojo code. A list of all commands are listed below, and you can learn more about each one by adding the `--help` option to the command (for example, `mojo precompile --help`). However, you may omit the `run` and `repl` commands. That is, you can run a Mojo file by simply passing the filename to `mojo`: mojo hello.mojo And you can start a REPL session by running `mojo` with no commands. You can check your current version with `mojo --version`. For version information, see all the [Mojo releases](https://mojolang.org/releases/). ## Commands [`run`](run.md) — Builds and executes a Mojo file. [`build`](build.md) — Builds an executable from a Mojo file. [`repl`](repl.md) — Launches the Mojo REPL. [`debug`](debug.md) — Launches the Mojo debugger using the command-line interface or an external editor. [`precompile`](precompile.md) — Precompiles a Mojo package. [`format`](format.md) — Formats Mojo source files. [`doc`](doc.md) — Compiles docstrings from a Mojo file. [`demangle`](demangle.md) — Demangles the given name. ## Options ### Diagnostic options #### `--version`, `-v` Prints the Mojo version and exits. ### Cache management options #### `--print-cache-location` Prints the Mojo compile cache (.mojo_cache) location and exits. #### `--clear-cache` Removes the Mojo compile cache (.mojo_cache) after confirmation. Pass '-f' / '--force' to skip the confirmation prompt. ### Common options #### `--help`, `-h` Displays help information. #### `--help-hidden` Displays help for hidden options. --- ## mojo precompile Precompiles a Mojo package. ## Synopsis ``` mojo precompile [options] ``` ## Description Precompiles a directory of Mojo source files into a binary package suitable to share and import into other Mojo programs and modules. A precompiled Mojo package is faster to build with compared to building it from source. It is not intended as a distributable format as it is tied to the version of the compiler that produced it. Loading a precompiled package using a compiler of a different version will error. Despite this, it is technically portable across different systems because it is not an architecture-specific format (it includes only non-elaborated code). The code becomes an architecture-specific executable only after it's imported into a Mojo program that is then compiled with `mojo build`. To create a Mojo package, first add an `__init__.mojo` file to your package directory. Then pass that directory name to this command, and specify the output path and filename with `-o`. For more information, see [Mojo modules and packages](https://mojolang.org/docs/manual/packages/). ## Options ### Output options #### `-o ` Sets the path and filename for the output package. The filename must end with `.mojoc` or `.mojopkg`. The filename given here defines the package name you can then use to import the code (minus the file extension). If you don't specify this option, a `.mojoc` file is generated in the current working directory, with a name based on the name of the input directory. ### Compilation options #### `-I ` Appends the given path to the list of directories to search for imported Mojo files. ### Compilation diagnostic options Controls how the Mojo compiler outputs diagnostics related to compiling and running Mojo source code. #### `--diagnose-missing-doc-strings` Emits diagnostics for missing or partial doc strings. #### `--max-notes-per-diagnostic ` When the Mojo compiler emits diagnostics, it sometimes also prints notes with additional information. This option sets an upper threshold on the number of notes that can be printed with a diagnostic. If not specified, the default maximum is 10. #### `--disable-builtins` Do not use builtins when create package. #### `--disable-warnings` Do not print warning messages. #### `--experimental-fixit` Automatically apply fix-its to the code, and rerun the command again after the fix-its are applied. WARNING: this feature is highly experimental and may result in irreversible data loss. #### `--experimental-export-fixit ` Export fix-its to a YAML file in clang-tidy format instead of applying them directly. The file can be applied using 'clang-apply-replacements'. WARNING: this feature is highly experimental. #### `--Werror` Treat warnings as errors. #### `--Wno-error` Do not treat warnings as errors. #### `--warn-on-unstable-apis` Warn when using unstable APIs from the standard library. #### `--ignore-incompatible-precompiled-file-errors` Ignore errors encountered when loading incompatible Mojo precompiled files. #### `--ignore-deprecated ` Suppress the deprecation warning for the given declaration (e.g. `Foo.bar`, or `some_fn` for a top-level declaration). ### Common options #### `--diagnostic-format ` The format in which diagnostics and error messages are printed. Must be one of "text" or "json" ("text" is the default). #### `--help`, `-h` Displays help information. #### `--help-hidden` Displays help for hidden options. --- ## mojo repl Launches the Mojo REPL. ## Synopsis ``` mojo repl [lldb-options] ``` ## Description Launches a Mojo read-evaluate-print loop (REPL) environment, which provides interactive development in the terminal. You can also start the REPL by running `mojo` without CLI arguments. Options and arguments are forwarded to the underlying LLDB tool, which runs the REPL. Beware: the REPL has known issues and isn't under active development. We don't offer any REPL stability guarantees. To check REPL status and future developments, watch the Mojo REPL roadmap discussion on https://forum.modular.com/t/mojo-repl-roadmap/1158. ## Options ### Common options #### `--help`, `-h` Displays help information. #### `--help-hidden` Displays help for hidden options. --- ## mojo run Builds and executes a Mojo file. ## Synopsis ``` mojo run [options] [path-arguments...] ``` ## Description Compiles the Mojo file at the given path and immediately executes it. Another way to execute this command is to simply pass a file to `mojo`. For example: mojo hello.mojo Options for this command itself, such as the ones listed below, must appear before the input file `path` argument. Any command line arguments that appear after the Mojo source file `path` are interpreted as arguments for that Mojo program. ## Options ### Compilation options #### `--optimization-level `, `-O`, `--no-optimization (LEVEL=0)` Sets the level of optimization to use at compilation. The value must be a number between 0 and 3. The default is 3. #### `-I ` Appends the given path to the list of directories to search for imported Mojo files. #### `-D ` Defines a named value that can be used from within the Mojo source file being executed. For example, `-Dfoo=42` defines a name `foo` that, when queried with the `std.defines` module from within the Mojo program, would yield the compile-time value `42`. #### `--debug-level `, `-g (LEVEL=full)`, `-g0 (LEVEL=none)`, `-g1 (LEVEL=line-tables)`, `-g2 (LEVEL=full)` Sets the level of debug info to use at compilation. The value must be one of: `none`, `line-tables`, or `full`. Default is `none`, except when using `mojo debug foo.mojo`, which defaults to `full`. Please note that there are issues when generating debug info for some Mojo programs that have yet to be addressed. #### `--num-threads `, `-j` Sets the maximum number of threads to use for compilation. The default is 0 (use all available threads). #### `--elaboration-error-include-prelude` Show elaboration error with locations in mojo startup modules (prelude). #### `--fp-mode ` Controls floating-point behavior as a comma-separated list of `feature=value` items (may be given more than once). The only feature is `contract`, one of `fast` (default) or `off`. `contract=fast` is like Clang's `-ffp-contract=fast`: it fuses `a + b*c` into an FMA across statements and breaking strict IEEE compliance. `contract=off` disables contraction. ### Target options #### `--target-triple ` Sets the compilation target triple. Defaults to the host target. #### `--target-cpu ` Sets the compilation target CPU. Defaults to the host CPU. #### `--target-features ` Sets the compilation target CPU features. Defaults to the host features. #### `--target-abi ` Sets the target ABI name (e.g. `lp64d`), recorded as a `target-abi` LLVM module flag. Unset by default. #### `--march ` Sets the architecture for which to generate code. #### `--mcpu ` Sets the CPU for which to generate code. #### `--mtune ` Sets the CPU for which to tune code. #### `--target-accelerator ` Sets the GPU or accelerator architecture for heterogeneous computing (e.g., sm_90 for NVIDIA H100, gfx942 for AMD MI300). #### `--print-effective-target` Print the effective target configuration after absorbing all command-line flags and exit. #### `--print-supported-targets` Print all available target names and exit. #### `--print-supported-cpus` Print valid CPU names for the specified target and exit. Requires --target-triple. #### `--print-supported-accelerators` Print all supported GPU and accelerator architectures and exit. ### Compilation diagnostic options Controls how the Mojo compiler outputs diagnostics related to compiling and running Mojo source code. #### `--diagnose-missing-doc-strings` Emits diagnostics for missing or partial doc strings. #### `--max-notes-per-diagnostic ` When the Mojo compiler emits diagnostics, it sometimes also prints notes with additional information. This option sets an upper threshold on the number of notes that can be printed with a diagnostic. If not specified, the default maximum is 10. #### `--disable-builtins` Do not use builtins when create package. #### `--disable-warnings` Do not print warning messages. #### `--experimental-fixit` Automatically apply fix-its to the code, and rerun the command again after the fix-its are applied. WARNING: this feature is highly experimental and may result in irreversible data loss. #### `--experimental-export-fixit ` Export fix-its to a YAML file in clang-tidy format instead of applying them directly. The file can be applied using 'clang-apply-replacements'. WARNING: this feature is highly experimental. #### `--Werror` Treat warnings as errors. #### `--Wno-error` Do not treat warnings as errors. #### `--warn-on-unstable-apis` Warn when using unstable APIs from the standard library. #### `--ignore-incompatible-precompiled-file-errors` Ignore errors encountered when loading incompatible Mojo precompiled files. #### `--ignore-deprecated ` Suppress the deprecation warning for the given declaration (e.g. `Foo.bar`, or `some_fn` for a top-level declaration). ### Linker options #### `-Xlinker ` Pass ARG to the linker. #### `--lld-path ` Overrides the path to the `lld` linker used when linking. Takes precedence over the `MODULAR_MOJO_MAX_LLD_PATH` environment variable and the `mojo-max.lld_path` configuration value. ### Experimental compilation options #### `--sanitize ` Turns on runtime checks. The following values are supported: `address` (detects memory issues), and `thread` (detects multi-threading issues). #### `--shared-libasan` Dynamically link the address sanitizer runtime. Requires address sanitization turned on with `--sanitize` option. #### `--debug-info-language ` Sets the language to emit as part of the debug info. The supported languages are: `Mojo`, and `C`. `Mojo` is the default. `C` is useful to enable rudimentary debugging and binary introspection in tools that don't understand Mojo, but is not required for `mojo debug`. ### Common options #### `--diagnostic-format ` The format in which diagnostics and error messages are printed. Must be one of "text" or "json" ("text" is the default). #### `--help`, `-h` Displays help information. #### `--help-hidden` Displays help for hidden options. --- ## Mojo standard library
All the data types, structs, traits, functions, and other APIs included with Mojo. The standard library provides nearly everything you'll need for writing Mojo programs, including basic data types like [`Int`](/docs/std/simd/#int) and [`SIMD`](/docs/std/simd/SIMD/), collection types like [`List`](/docs/std/collections/list/List/), reusable [algorithms](/docs/std/algorithm/), and modules to support [GPU programming](https://max.modular.com/api/mojo/max/gpu/). ## Packages * [​`algorithm`](/docs/std/algorithm/): High performance data operations including vectorization, functional map, and tiling. * [​`atomic`](/docs/std/atomic/): Atomic operations and memory orderings. * [​`base64`](/docs/std/base64/): Binary data encoding: base64 and base16 encode/decode functions. * [​`benchmark`](/docs/std/benchmark/): Performance benchmarking: statistical analysis and detailed reports. * [​`bit`](/docs/std/bit/): Bitwise operations: manipulation, counting, rotation, and power-of-two utilities. * [​`builtin`](/docs/std/builtin/): Language foundation: built-in types, traits, and fundamental operations. * [​`collections`](/docs/std/collections/): Core data types: List, Dict, Set, Optional, String, and other collections. * [​`compile`](/docs/std/compile/): Runtime function compilation and introspection: assembly, IR, linkage, metadata. * [​`complex`](/docs/std/complex/): Complex numbers: SIMD types, scalar types, and operations. * [​`documentation`](/docs/std/documentation/): Documentation built-ins: decorators and utilities for doc generation. * [​`ffi`](/docs/std/ffi/): Foreign function interface (FFI) for calling C code and loading libraries. * [​`format`](/docs/std/format/): Provides formatting traits for converting types to text. * [​`hashlib`](/docs/std/hashlib/): Cryptographic and non-cryptographic hashing with customizable algorithms. * [​`io`](/docs/std/io/): Core I/O operations: console input/output, file handling, writing traits. * [​`iter`](/docs/std/iter/): Iteration traits and utilities: Iterable, IterableOwned, Iterator, enumerate, zip, map. * [​`itertools`](/docs/std/itertools/): Iterator tools for lazy sequence generation and transformation. * [​`logger`](/docs/std/logger/): Logging with configurable severity levels. * [​`math`](/docs/std/math/): Math functions and constants: trig, exponential, logarithmic, and special functions. * [​`memory`](/docs/std/memory/): Low-level memory management: pointers, allocations, address spaces. * [​`origin`](/docs/std/origin/): Defines Mojo's origin types. * [​`os`](/docs/std/os/): OS interface layer: environment, filesystem, process control. * [​`pathlib`](/docs/std/pathlib/): Filesystem path manipulation and navigation. * [​`prelude`](/docs/std/prelude/): Standard library prelude: fundamental types, traits, and operations auto-imported. * [​`pwd`](/docs/std/pwd/): Password database lookups for user account information. * [​`python`](/docs/std/python/): Python interoperability: import packages and modules, call functions, type conversion. * [​`random`](/docs/std/random/): Pseudorandom number generation with uniform and normal distributions. * [​`reflection`](/docs/std/reflection/): Compile-time reflection utilities for introspecting Mojo types and functions. * [​`runtime`](/docs/std/runtime/): Runtime services: runtime initialization and worker-thread queries. * [​`stat`](/docs/std/stat/): File type constants and detection from stat system calls. * [​`subprocess`](/docs/std/subprocess/): Execute external processes and commands. * [​`sys`](/docs/std/sys/): System runtime: I/O, hardware info, intrinsics, compile-time utils. * [​`tempfile`](/docs/std/tempfile/): Manage temporary files and directories: create, locate, and cleanup. * [​`testing`](/docs/std/testing/): Unit testing: Assertions (equal, true, raises) and test suites. * [​`time`](/docs/std/time/): Timing operations: monotonic clocks, performance counters, sleep, time_function. * [​`traits`](/docs/std/traits/): Core object-lifetime and value-semantics traits. * [​`utils`](/docs/std/utils/): General utils: indexing, variants, static tuples, and thread synchronization. ## Modules * [​`simd`](/docs/std/simd/): Implements SIMD primitives and abstractions.