Skip to main content

Types

All values in Mojo have an associated data type. Most of the types are nominal types, defined by a struct. 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 and 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.

Integers and unsigned integers

Mojo supports both signed (Int) and unsigned (UInt) integer types. These include both fixed-size and general types:

  • If you need a fixed-size integer, Mojo provides explicit-width integer types such as Int8, Int16, UInt32, and UInt64. These fixed-precision integer types are aliases to the SIMD type.

  • Use the general Int or UInt types when you don’t require a specific bit width.

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. Similarly, UInt represents an unsigned integer that uses the system’s default word size.

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 nameDescription
Int88-bit signed integer
Int1616-bit signed integer
Int3232-bit signed integer
Int6464-bit signed integer
Int128128-bit signed integer
Int256256-bit signed integer
Table 2. Mojo unsigned integer types
Type nameDescription
UInt88-bit unsigned integer
UInt1616-bit unsigned integer
UInt3232-bit unsigned integer
UInt6464-bit unsigned integer
UInt128128-bit unsigned integer
UInt256256-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 nameSignExponentMantissa
Float641 bit11 bits52 bits
Float321 bit8 bits23 bits
Float161 bit5 bits10 bits

Numbers with exponent values of all ones or all zeros represent special values, allowing floating-point numbers to represent infinity, negative infinity, signed zeros, and not-a-number (NaN). For more details on how floating-point numbers are represented, see 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:

var big_num = 1.0e16
var bigger_num = big_num + 1.0
print(big_num == bigger_num)
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() 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.

Table 4. Mojo floating-point types
Type nameDescriptionCPU/GPU Support
Float1616-bit floating-point
(IEEE 754-2008 binary16)
CPU and GPU
Float3232-bit floating-point
(IEEE 754-2008 binary32)
CPU and GPU
Float6464-bit floating-point
(IEEE 754-2008 binary64)
CPU and GPU
BFloat1616-bit floating-point
(16-bit version of IEEE 754 binary32)
CPU and GPU
Float4_e2m1fn4-bit floating-point
(e2m1 format from Open Compute MX specification — finite values and NaN only, no infinities)
GPU
Float8_e5m28-bit floating-point
(OFP8 e5m2 format)
GPU
Float8_e5m2fnuz8-bit floating-point
(AMD-only e5m2fnuz format — finite values and NaN only, no infinities)
GPU
Float8_e4m3fn8-bit floating-point
(OFP8 e4m3fn format — finite values and NaN only, no infinities)
GPU
Float8_e4m3fnuz8-bit floating-point
(AMD-only e4m3fnuz format — finite values and NaN only, no infinities)
GPU

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 and 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
FormatExamplesNotes
Integer literal1760Integer literal, in decimal format.
Hexadecimal literal0xaa, 0xFFInteger literal, in hexadecimal format.
Hex digits are case-insensitive.
Octal literal0o77Integer literal, in octal format.
Binary literal0b0111Integer literal, in binary format.
Floating-point literal3.14, 1.2e9Floating-point literal.
Must include the decimal point to be interpreted as floating-point.

At compile time, the literal types are arbitrary-precision (also called infinite-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—Int for integer values, and Float64 for floating-point values. (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.

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)
1.0 0.99999999999999978

SIMD and DType

To support high-performance numeric processing, Mojo uses the 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:

  • 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:

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:

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)
[2, 6, 15, 28]

Scalar values

The SIMD module defines several comptime values that function as type aliases—shorthand names for different types of SIMD vectors. In particular, the Scalar type is just a SIMD vector with a single element. The numeric types listed in Table 1, like Int8 and Float32 are actually type aliases for different types of scalar values:

comptime Scalar = SIMD[size=1]
comptime Int8 = Scalar[DType.int8]
comptime Float32 = Scalar[DType.float32]

This may seem a little confusing at first, but it 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 that act as identifiers for the different data types, like DType.int8 and DType.float32. You use these comptime members when declaring a SIMD vector:

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).

from 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]()
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

Constructors and implicit conversion documents the circumstances in which Mojo automatically converts a value from one type to another. Importantly, numeric operators don't automatically narrow or widen operands to a common type.

You can explicitly convert a SIMD value to a different SIMD type either by invoking its cast() method or by passing it as an argument to the constructor of the target type. For example:

simd1 = SIMD[DType.float32, 4](2.2, 3.3, 4.4, 5.5)
simd2 = SIMD[DType.int16, 4](-1, 2, -3, 4)
simd3 = simd1 * simd2.cast[DType.float32]()  # Convert with cast() method
print("simd3:", simd3)
simd4 = simd2 + SIMD[DType.int16, 4](simd1)  # Convert with SIMD constructor
print("simd4:", simd4)
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 constructor of the target type. For example:

var my_int: Int16 = 12                 # SIMD[DType.int16, 1]
var my_float: Float32 = 0.75           # SIMD[DType.float32, 1]
result = Float32(my_int) * my_float    # Result is SIMD[DType.float32, 1]
print("Result:", result)
Result: 9.0

You can convert a scalar value of any numeric type to Int by passing the value to the Int() constructor method. Additionally, you can pass an instance of any struct that implements the Intable trait or IntableRaising trait to the Int() constructor to convert that instance to an Int.

You can convert an Int or IntLiteral value to the UInt type by passing the value to the UInt() constructor. You can't convert other numeric types to UInt directly, though you can first convert them to Int and then to UInt.

Strings

Mojo's String type represents a mutable string. (For Python programmers, note that this is different from Python's standard string, which is immutable.) Strings support a variety of operators and common methods.

var s: String = "Testing"
s += " Mojo strings"
print(s)
Testing Mojo strings

Most standard library types conform to the Stringable trait, which represents a type that can be converted to a string. Use String(value) to explicitly convert a value to a string:

var s = "Items in list: " + String(5)
print(s)
Items in list: 5

Or use String.write to take variadic Stringable types, so you don't have to call String() on each value:

var s = String("Items in list: ", 5)
print(s)
Items in list: 5

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:

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:

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:

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.

var conditionA = False
var conditionB: Bool
conditionB = not conditionA
print(conditionA, conditionB)
False True

Many types have a boolean representation. Any type that implements the 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 type represents an immutable tuple consisting of zero or more values, separated by commas. Tuples can consist of multiple types and you can index into tuples in multiple ways.

# Tuples are immutable and can hold multiple types
example_tuple = Tuple[Int, String](1, "Example")

# Assign multiple variables at once
x, y = example_tuple
print(x, y)

# Get individual values with an index
s = example_tuple[1]
print(s)
1 Example
Example

You can also create a tuple without explicit typing.

example_tuple = (1, "Example")
s = example_tuple[1]
print(s)
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, a dynamically-sized array of items.
  • Dict, an associative array of key-value pairs.
  • Set, an unordered collection of unique items.
  • Optional represents a value that may or may not be present.

The collection types are generic 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. For example, you can create a List of Int values like this:

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].

# Inferred type == List[Int]
var l1 = [1, 2, 3, 4]

Where you need a more flexible collection, the 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 for more information.)

The following sections give brief introduction to the main collection types.

List

List is a dynamically-sized array of elements. List elements need to conform to the Copyable trait. Most of the common standard library primitives, like Int, String, and SIMD conform to this trait. You can create a List by passing the element type as a parameter, like this:

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.

var list = [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=", ")
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:

    # List literal, element type infers to Int.
    var nums = [2, 3, 5]

    You can also use an explicit type if you want a specific element type:

    var list : List[UInt8] = [2, 3, 5]

    You can also use list "comprehensions" for compact conditional initialization:

    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.

    # Does not work
    print(list)

    As shown above, you can print the individual elements in a list as long as they're a Stringable type.

  • Iterating a List returns an immutable reference to each item:

var list = [2, 3, 4]
for item in list:
      print(item, end=", ")
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:

var list = [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])
2, 3, 4,
After loop: 0 0 0

You can see that the original loop entries were modified.

Dict

The 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:

# 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 constructor syntax:

var values = Dict[String, Float64]()

The dictionary's key type must conform to the KeyElement trait, and value elements must conform to the 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, which are copied into the declared name by default, but you can use the ref marker to avoid the copy:

var d: Dict[String, Float64] = {
    "plasticity": 3.1,
    "elasticity": 1.3,
    "electricity": 9.7
}
for item in d.items():
    print(item.key, item.value)
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 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 generic and the element type must conform to the KeyElement trait. Like lists and dictionaries, sets support standard literal syntax, as well as generator comprehensions:

i_like = {"sushi", "ice cream", "tacos", "pho"}
you_like = {"burgers", "tacos", "salad", "ice cream"}
we_like = i_like.intersection(you_like)

print("We both like:")
for item in we_like:
    print("-", item)
We both like:
- ice cream
- tacos

Optional

An Optional represents a value that may or may not be present. Like the other collection types, it is generic, and can hold any type that conforms to the Copyable trait.

# 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.

var opt: Optional[String] = "Testing"
if opt:
    var value_ref = opt.value()
    print(value_ref)
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:

var custom_greeting: Optional[String] = None
print(custom_greeting.or_else("Hello"))

custom_greeting = "Hi"
print(custom_greeting.or_else("Hello"))
Hello
Hi

Register-passable, memory-only, and trivial types

In various places in the documentation you'll see references to register-passable, memory-only, and trivial types. Register-passable and memory-only types are distinguished based on how they hold data:

  • Register-passable types are composed exclusively of fixed-size data types, which can (theoretically) be stored in a machine register. A register-passable type can include other types, as long as they are also register-passable. Int, Bool, and SIMD, for example, are all register-passable types. So a register-passable struct could include Int and Bool fields, but not a String field. Register-passable types are declared with the @register_passable decorator.

  • Memory-only types consist of all other types that aren't specifically designated as register-passable types. These types often have pointers or references to dynamically-allocated memory. String, List, and Dict are all examples of memory-only types.

Register-passable types have a slightly different lifecycle than memory-only types, which is discussed in Life of a value.

There are also a number of low-level differences in how the Mojo compiler treats register-passable types versus memory-only types, which you probably won't have to think about for most Mojo programming. For more information, see the @register_passable decorator reference.

Our long-term goal is to make this distinction transparent to the user, and ensure all APIs work with both register-passable and memory-only types. But right now you will see a few standard library types that only work with register-passable types or only work with memory-only types.

In addition to these two categories, Mojo also has "trivial" types. Conceptually a trivial type is simply a type that doesn't require any custom logic in its lifecycle methods. The bits that make up an instance of a trivial type can be copied or moved without any knowledge of what they do. Currently, trivial types are declared using the @register_passable(trivial) decorator. Trivial types shouldn't be limited to only register-passable types, so in the future we intend to separate trivial types from the @register_passable decorator.

AnyType and AnyTrivialRegType

Two other things you'll see in Mojo APIs are references to AnyType and AnyTrivialRegType. These are effectively metatypes, that is, types of types.

  • AnyType is a trait that represents a type with a destructor. You'll find more discussion of it on the Traits page.
  • AnyTrivialRegType is a metatype representing any Mojo type that's marked as a trivial type.

You'll see them in signatures like this:

fn any_type_function[ValueType: AnyTrivialRegType](value: ValueType):
    ...

You can read this as any_type_function has an argument, value of type ValueType, where ValueType is a register-passable type, determined at compile time.

There is still some code like this in the standard library, but it's gradually being migrated to more generic code that doesn't distinguish between register-passable and memory-only types.

Was this page helpful?