For the complete Mojo documentation index, see llms.txt. Markdown versions of all pages are available by appending .md to any URL (e.g. /docs/manual/basics.md).
Mojo compilation feature toggles
Mojo provides several mechanisms for compile-time feature gating and configuration:
- Compile-time defines (
-Dandsys.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:
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:
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:
mojo run -Dmode=release hello.mojo
Code:
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:
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 whichget_defined_*[]()function reads it-D KEY: defines a flag with no value.is_defined[]()returnsTrue. Useis_defined[]()for presence checks-D KEY=42: numeric values can be read withget_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:
mojo -Dverbose hello.mojo
Code:
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:
1true,True,TRUEon,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 |
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:
mojo -D max_threads=8 app.mojo
Code:
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:
mojo -D max_threads=8 app.mojo
Code:
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:
mojo -D mode=release app.mojo
Code:
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:
mojo -D mode=release app.mojo # release
# or
mojo app.mojo # debug (default)
Code:
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:
mojo -D dtype=float8_e4m3fn -D ctype=bfloat16 app.mojo
Code:
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_apple_silicon()CompilationTarget.is_apple_m1()throughCompilationTarget.is_apple_m5()
Instruction set detection
Detect target instruction set extensions with APIs such as:
CompilationTarget.has_avx512f()CompilationTarget.has_neon()
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:
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 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.
Read these values through sys.compile, which exposes them as the
compile-time values: OptimizationLevel, DebugLevel, and
SanitizeAddress.
For example:
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=<value> | String | none, safe (default), all, warn |
debug_assert() reads this value directly. Supported assertion levels are:
none: disable all assertionssafe(default in non-debug builds): only run assertions taggedassert_mode="safe"all: run everydebug_assert()callwarn: run every assertion, but emit warnings instead of aborting
For example:
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:
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:
# Bool form: always evaluates the condition.
debug_assert(len(data) > 0, "data must not be empty")
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:
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:
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.levelstores an integer from0to3.DebugLevel.levelstores one of the strings"none","line-tables", or"full".
from std.sys.compile import DebugLevel
def main():
comptime if DebugLevel.level == "full":
print(
"full debug info emitted: enabling source-aware logging"
)