This document describes conventions that Mojo standard library code should adhere to. Its coverage ranges from non-semantic conventions like code formatting, to semantics like value lifecycle behavior that standard library types should generally conform to.
The Mojo standard library uses the following high-level organization:
- Group related functions within the same file.
- Group related files within the same directory.
- Do not add dependencies to the
stdlibmodule because, by definition, it is required to be a leaf dependency.
> stdlib # stdlib root directory
> doc # folder containing markdown documentation
> scripts # scripts for compiling and running the tests
> src # stdlib mojo source files
> builtin # mojo source for stdlib builtins
> collections # mojo source for stdlib collections
> memory # mojo source for stdlib memory primitives
...
> test # stdlib unit tests
All Mojo source files must end with the extension .mojo or .🔥.
Mojo provides a command line formatting utility, mojo format, designed to
automatically format your code according to the official Mojo style guidelines.
It adjusts indentation, spacing, and line breaks, making code more readable and
consistent.
> mojo format example.mojo
All done! ✨ 🍰 ✨
1 file left unchanged.Unless otherwise noted, Mojo standard library code should follow the formatting
produced by mojo format.
It is advised, to avoid forgetting, to set-up pre-commit, which will format
your changes automatically at each commit, and will also ensure that you
always have the latest linting tools applied.
To do so, install pre-commit:
pixi x pre-commit installand that's it!
Mojo provides a command line utility, mojo doc, to validate the API doc
strings in your code. This ensures that your doc strings are correctly
formatted and consistent with the Mojo style guidelines.
Note that you should not have any warnings.
> mojo doc --diagnose-missing-doc-strings --validate-doc-strings -o /dev/null stdlib/src/Note that this is also included in the pre-commit. So if you have pre-commit
enabled, this will run automatically before committing. If you want to run it
manually with pre-commit, just run
pixi x pre-commit run --all-files- Use vertical whitespace only as needed to organize code into logical sections.
Every file in the open source Mojo standard library should begin with the following license information header:
# ===----------------------------------------------------------------------=== #
# Copyright (c) 2025, Modular Inc. All rights reserved.
#
# Licensed under the Apache License v2.0 with LLVM Exceptions:
# https://llvm.org/LICENSE.txt
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# ===----------------------------------------------------------------------=== #Code in the Mojo standard library should use the following conventional structure of header comments separating the various kinds of methods that can be defined on structs.
# ===-----------------------------------------------------------------------===#
# MyStruct
# ===-----------------------------------------------------------------------===#
struct MyStruct(Sized, Stringable):
"""Description goes here."""
# ===-------------------------------------------------------------------===#
# Aliases
# ===-------------------------------------------------------------------===#
alias factor = 5
# ===-------------------------------------------------------------------===#
# Fields
# ===-------------------------------------------------------------------===#
var field: Int
# ===-------------------------------------------------------------------===#
# Life cycle methods
# ===-------------------------------------------------------------------===#
fn __init__(...)
fn __moveinit__(...)
fn __copyinit__(...)
fn __del__(...)
# ===-------------------------------------------------------------------===#
# Factory methods
# ===-------------------------------------------------------------------===#
@staticmethod
fn foo(...) -> Self[...]
# ===-------------------------------------------------------------------===#
# Operator dunders
# ===-------------------------------------------------------------------===#
# Anything that "backs" special syntax: [..], *, +, /, //, etc...
fn __getitem__
fn __setitem__
fn __getattr__
fn __setattr__
fn __iter__ # `for x in self`
fn __next__
fn __contains__ # `x in self`
fn __is__ # `x is self`
fn __add__
fn __iadd__
# ===-------------------------------------------------------------------===#
# Trait implementations
# ===-------------------------------------------------------------------===#
fn __bool__
fn __len__
fn __str__
fn __abs__
# ===-------------------------------------------------------------------===#
# Methods
# ===-------------------------------------------------------------------===#
fn unsafe_ptr(..) # e.g.We want to be a good member of the Python family and aim to become a full superset, so we inherit naming from the Python standard library, including any inconsistencies. These naming inconsistencies are the only exceptions to the naming conventions outlined below.
There are several ways to capitalize and separate words, known as "case styles." By following the same set of case styles in our code, Mojo developers ensure their code is accessible and understandable to others in the community.
This first table is just a definition of the various "case styles."
| Case style | Description | Example |
|---|---|---|
snake_case |
All lowercase with underscores | variable_name |
PascalCase |
Each word starts with an uppercase letter | StructName |
SCREAMING_SNAKE_CASE |
All uppercase with underscores | CONSTANT_VALUE |
kebab-case |
All lowercase with hyphens | project-name |
flatcase |
All lowercase without separators | basename |
The following table shows our preferred use of different case styles.
| Code kind | Example | Case style |
|---|---|---|
fn / def |
fn engage_hyperdrive() |
snake_case |
struct |
struct Point |
PascalCase |
trait |
trait Copyable |
PascalCase |
enum |
enum StatusCode |
PascalCase |
var |
var the_value = 5 |
snake_case |
module / package |
io.mojo / os/__init__.mojo |
flatcase / snake_case |
| dunder | __copyinit__ |
flatcase |
| decorator | @register_passable |
snake_case |
| Parameters — type or value | ||
alias type |
alias Int8 = Scalar[DType.int8] |
PascalCase |
alias value global / local scope |
alias CHUNK_SIZE = 32 / alias chunk_size = 32 |
SCREAMING_SNAKE_CASE / snake_case |
struct type parameter |
struct List[ElementType: Movable] |
PascalCase |
struct value parameter |
struct Array[ElementType: Movable, length: Int] |
snake_case |
fn type parameter |
fn do_it[Action: Actionable](action: Action) |
PascalCase |
fn value parameter |
fn repeat[count: Int]() |
snake_case |
Although these are our style conventions, not all code currently adheres to it. When preparing a new change, it is important to adhere to the style and naming conventions already established in that module. Therefore, if the module you are working on uses a different style, continue using that style to maintain consistency. We are not currently accepting pull requests that propose extensive formatting or renaming changes.
struct LinkedList[T: Movable] # 🔴 Avoid
struct LinkedList[ElementType: Movable] # 🟢 Preferredstruct Array[length: Int, ElementType: Movable] # 🔴 Avoid
struct Array[ElementType: Movable, length: Int] # 🟢 PreferredConsider using the Some[] utility if a named (and inferred) type parameter
is not reused in a function signature or body.
fn foo[Str: Stringable, //](arg: Str): ... # 🔴 Avoid
fn foo(arg: Some[Stringable]): ... # 🟢 PreferredAvoid using the Some[] utility if a named type parameter is reused in a
function signature or body.
fn foo(arg0: Some[Stringable], arg1: type_of(arg0)): ... # 🔴 Avoid
fn foo[Str: Stringable, //](arg0: Str, arg1: Str): ... # 🟢 Preferredvar copy = original # 🔴 Avoid
var copy = MyStruct(original) # 🟢 PreferredWhere you intend to make a copy, favor an explicit copy constructor to make your intention clear.
Copying @register_passable types like Int, Bool, Pointer, and SIMD is
safe and inexpensive. However, copying types that dynamically allocate memory
can be expensive. This includes common types like List, Dict, Set,
and String.
Some standard library types allow implicit copies where they shouldn’t. We will resolve this shortly as new Mojo language features are shipped to help with this very situation.
When designing a new type, don’t allow implicit copies unless
the copy is trivial (order O(1)). In other words, don’t define a
__copyinit__() function if the copy is expensive. Instead, define an
explicit copy constructor: an __init__() constructor that takes a value of
the same type:
struct MyStruct:
# Invoked as `MyStruct(other)`
fn __init__(out self, other: Self):
# do a deep copy of MyStructMany standard library types provide a copy() method that creates an explicit copy:
var original = List[Int]()
var explicit_copy = original.copy() # 🟢 PreferredThis pattern is used throughout the stdlib for types like Optional, String,
and collections.
- Explicitly import entities used (functions, structs, aliases), rather than rely on transitive imports.
- Import only what you use; in general, avoid using
from some_package import *. - Import statements should be sorted lexicographically.
Every public function and public struct (including data fields) in the standard library must have docstrings (code comments that describe the API behavior). Mojo includes tooling to ensure that public functions include docstrings.
You can run ./stdlib/scripts/check-docstrings.py to validate
docstrings. If the command exits with a 0 exit code, the docstrings are
compliant; otherwise, an error will be shown. This is also enforced by the LSP
with warnings for anything that doesn’t conform, you can generate docstrings
based on the signature using an LSP Quick Fix:
We follow Google's Python convention for docstrings outlined here which looks like this:
fn add_param_arg[foo: Int](bar: Int) -> Int:
"""[summary].
Parameters:
foo: [description].
Args:
bar: [description].
Returns:
[description].
"""
return foo + barThe additions to the Google style guide for docstrings are Parameters: and
Constraints:.
Constraints: should be used to document requirements when using the
constrained builtin function:
fn add_param_arg[foo: Int](bar: Int) -> Int:
"""Shortened doc string.
Constraints:
`foo` must be more than 0.
"""
constrained[foo > 0]()
return foo + barFor more detailed style guidelines, see the Mojo docstring style guide.
You should use assertions liberally when there is a condition you expect for the following code to behave correctly. Assertions may be present even in "unsafe" methods and types. You should format the message to be descriptive with the expected and actual values:
actual = 41
expected = 42
debug_assert(actual == expected, "expected: ", expected, " but got: ", actual)Make sure that you don't allocate anything in the call to debug_assert to
ensure there is no runtime penalty when assertions are disabled. If you must
have a side-effect in the condition such as allocating a String, you can pass
a closure as a parameter that only runs when assertions are enabled:
tensor = Tensor[DType.uint8, 1](TensorShape(1), cpu_device())
fn _test_cpu() capturing -> Bool:
return "cpu" in String(tensor._device)
debug_assert[_test_cpu]("This code is only runnable on CPU")When writing code that uses @parameter if to tailor logic based on the target
hardware platform or features, do not use trailing else statements that
"fallthrough" to a particular hardware vendor.
This leads to poor error messages when an unsupported hardware vendor is targeted.
# 🔴 Avoid
@parameter
if is_nvidia_gpu():
return "llvm.nvvm..."
else:
# BAD: Assumes only non-NVIDIA target is AMD
return "llvm.amdgcn..."Always gate hardware-specific logic on an explicit check that that vendor or feature is being targeted:
# 🟢 Prefer
@parameter
if is_nvidia_gpu():
...
elif is_amd_gpu():
...
else:
return CompilationTarget.unsupported_target_error[Foo]()In cases where a generic, cross-platform compatible fallback implementation
is available, it is okay to use an unguarded else condition:
@always_inline("nodebug")
fn prefetch[...](...):
@parameter
if is_nvidia_gpu():
inlined_assembly["prefetch.global.L2 [$0];", ...](...)
else:
llvm_intrinsic["llvm.prefetch", NoneType](...)All test filenames should be prefixed with test_.
For example test_sort.mojo.
- Tests should mirror the source structure
- e.g.,
stdlib/collections/list.mojotests are intest/collections/test_list.mojo
- e.g.,
- Use the
testingmodule assertions (assert_equal,assert_true, etc.) for new tests - Test files should focus on testing the public API and critical edge cases
- Benchmarks are located in the
benchmarks/directory - Use the
benchmarkmodule for performance testing - Benchmark files should be prefixed with
bench_(e.g.,bench_sort.mojo)
- See the bazel usage docs for more detaiils on how to run tests.
