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 a 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's most basic numeric type is Int, which represents a signed integer of the largest size supported by the system—typically 64 bits or 32 bits.

Mojo also has built-in types for integer, unsigned integer, and floating-point values of various precisions:

Type nameDescription
Int88-bit signed integer
UInt88-bit unsigned integer
Int1616-bit signed integer
UInt1616-bit unsigned integer
Int3232-bit signed integer
UInt3232-bit unsigned integer
Int6464-bit signed integer
UInt6464-bit unsigned integer
Float1616-bit floating point number (IEEE 754-2008 binary16)
Float3232-bit floating point number (IEEE 754-2008 binary32)
Float6464-bit floating point number (IEEE 754-2008 binary64)
Table 1. Numeric types with specific precision

The types in Table 1 are actually all aliases to a single type, SIMD, which is discussed later.

All of the numeric types support the usual numeric and bitwise operators. The math module provides a number of additional math functions.

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 and unsigned integers

Mojo supports both signed (Int) and unsigned (UInt) integers. You can use the general Int or UInt types when you do not require a specific bit width. Note that any alias to a fixed-precision type will be of type SIMD.

You might prefer to use unsigned integers over signed integers in conditions where you don't need negative numbers, are not writing for a public API, or need additional range.

Mojo's UInt type represents an unsigned integer of the word size of the CPU, which is 64 bits on 64-bit CPUs and 32 bits on 32-bit CPUs. If you wish to use a fixed size unsigned integer, you can use UInt8, UInt16, UInt32, or UInt64, which are aliases to the SIMD type.

Signed and unsigned integers of the same bit width can represent the same number of values, but have different ranges. For example, an Int8 can represent 256 values ranging from -128 to 127. A UInt8 can also represent 256 values, but represents a range of 0 to 255.

Signed and unsigned integers also have different overflow behavior. When a signed integer overflows outside the range of values that its type can represent, the value overflows to negative numbers. For example, adding 1 to var si: Int8 = 127 results in -128.

When an unsigned integer overflows outside the range of values that its type can represent, the value overflows to zero. So, adding 1 to var ui: UInt8 = 255 is equal to 0.

Floating-point numbers

Floating-point types represent real numbers. Because not all real numbers can be expressed in a finite number of bits, floating-point numbers can't represent every value exactly.

The floating-point types listed in Table 1—Float64, Float32, and Float16—follow the IEEE 754-2008 standard for representing floating-point values. Each type includes a sign bit, one set of bits representing an exponent, and another set representing the fraction or mantissa. Table 2 shows how each of these types are represented in memory.

Type nameSignExponentMantissa
Float641 bit11 bits52 bits
Float321 bit8 bits23 bits
Float161 bit5 bits10 bits
Table 2. Details of floating-point types

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 numbers are represented, see IEEE 754 on Wikipedia.

A few things to note with floating-point values:

  • Rounding errors. Rounding may produce unexpected results. For example, 1/3 can't be represented exactly in these floating-point formats. The more operations you perform with floating-point numbers, the more the rounding errors accumulate.

  • Space between consecutive numbers. The space between consecutive numbers is variable across the range of a floating-point number format. For numbers close to zero, the distance between consecutive numbers is very small. For large positive and negative numbers, the space between consecutive numbers is greater than 1, so it may not be possible to represent consecutive integers.

Because the values are approximate, it is rarely useful to compare them with the equality operator (==). Consider the following example:

var big_num = 1.0e16
var bigger_num = big_num+1.0
print(big_num == bigger_num)
var big_num = 1.0e16
var bigger_num = big_num+1.0
print(big_num == bigger_num)
True

Comparison operators (< >= and so on) work with floating point numbers. You can also use the math.isclose() function to compare whether two floating-point numbers are equal within a specified tolerance.

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 3 summarizes the literal formats you can use to represent numbers.

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.
Table 3. Numeric literal formats

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)
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)
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)
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 type aliases that are shorthand 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:

alias Scalar = SIMD[size=1]
alias Int8 = Scalar[DType.int8]
alias Float32 = Scalar[DType.float32]
alias Scalar = SIMD[size=1]
alias Int8 = Scalar[DType.int8]
alias 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 aliases that act as identifiers for the different data types, like DType.int8 and DType.float32. You use these aliases when declaring a SIMD vector:

var v: SIMD[DType.float64, 16]
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]()
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.

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)
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 str(value) to explicitly convert a value to a string:

var s = str("Items in list: ") + str(5)
print(s)
var s = str("Items in list: ") + str(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:

var s = "A very long string which is "
"broken into two literals for legibility."
var 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:

var s = """
Multi-line string literals let you
enter long blocks of text, including
newlines."""
var 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.

Unlike IntLiteral and FloatLiteral, StringLiteral doesn't automatically materialize to a runtime type. In some cases, you may need to manually convert StringLiteral values to String using the built-in str() method.

# Variable is type `StringLiteral`
var s1 = "Example"

# Variable is type `String`
var s2: String = "Example"

# Variable is type `String`
var s3 = str("Example")
# Variable is type `StringLiteral`
var s1 = "Example"

# Variable is type `String`
var s2: String = "Example"

# Variable is type `String`
var s3 = str("Example")

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)
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.get[1, String]()
print(s)
# 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.get[1, String]()
print(s)
1 Example Example

You can also create a tuple without explicit typing. Note that if we declare the same tuple from the previous example with implicit typing instead of explicit, we must also convert "Example" from type StringLiteral to type String.

example_tuple = (1, str("Example"))
s = example_tuple.get[1, String]()
print(s)
example_tuple = (1, str("Example"))
s = example_tuple.get[1, String]()
print(s)
Example

When defining a function, you can explicitly declare the type of tuple elements in one of two ways:

def return_tuple_1() -> Tuple[Int, Int]:
return Tuple[Int, Int](1, 1)

def return_tuple_2() -> (Int, Int):
return (2, 2)
def return_tuple_1() -> Tuple[Int, Int]:
return Tuple[Int, Int](1, 1)

def return_tuple_2() -> (Int, Int):
return (2, 2)

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
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 == Int
var l1 = List(1, 2, 3, 4)
# Inferred type == Int
var l1 = List(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 CollectionElement trait, which just means that the items must be copyable and movable. 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]()
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.

from collections import List

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

from collections import List

var list = 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.

There are some notable limitations when using List:

  • You can't currently initialize a list from a list literal, like this:

    # Doesn't work!
    var list: List[Int] = [2, 3, 5]
    # Doesn't work!
    var list: List[Int] = [2, 3, 5]

    But you can use variadic arguments to achieve the same thing:

    var list = List(2, 3, 5)
    var list = List(2, 3, 5)
  • You can't print() a list, or convert it directly into a string.

    # Does not work
    print(list)
    # 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 currently returns a Reference to each item, not the item itself. You can access the item using the dereference operator, []:

#: from collections import List
var list = List(2, 3, 4)
for item in list:
print(item[], end=", ")
#: from collections import List
var list = List(2, 3, 4)
for item in list:
print(item[], end=", ")
2, 3, 4,

Subscripting in to a list, however, returns the item directly—no need to dereference:

#: from collections import List
#: var list = List[Int](2, 3, 4)
for i in range(len(list)):
print(list[i], end=", ")
#: from collections import List
#: var list = List[Int](2, 3, 4)
for i in range(len(list)):
print(list[i], end=", ")
2, 3, 4,

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, like this:

var values = Dict[String, Float64]()
var values = Dict[String, Float64]()

The dictionary's key type must conform to the KeyElement trait, and value elements must conform to the CollectionElement 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, so you need to use the dereference operator [] as shown in the following example:

from collections import Dict

var d = Dict[String, Float64]()
d["plasticity"] = 3.1
d["elasticity"] = 1.3
d["electricity"] = 9.7
for item in d.items():
print(item[].key, item[].value)
from collections import Dict

var d = Dict[String, Float64]()
d["plasticity"] = 3.1
d["elasticity"] = 1.3
d["electricity"] = 9.7
for item in d.items():
print(item[].key, item[].value)
plasticity 3.1000000000000001 elasticity 1.3 electricity 9.6999999999999993

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.

from collections import Set

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

print("We both like:")
for item in we_like:
print("-", item[])
from collections import Set

i_like = Set("sushi", "ice cream", "tacos", "pho")
you_like = Set("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 CollectionElement 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
# 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] = str("Testing")
if opt:
var value_ref = opt.value()
print(value_ref)
var opt: Optional[String] = str("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 = str("Hi")
print(custom_greeting.or_else("Hello"))

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

custom_greeting = str("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.

    Register-passable types are always passed by value (that is, the values are copied).

  • Memory-only types consist of any types that don't fit the description of register-passable types. In particular, these types usually have pointers or references to dynamically-allocated memory. String, List, and Dict are all examples of memory-only types.

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 some 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 represents any Mojo type. Mojo treats AnyType as a special kind of trait, and you'll find more discussion of it on the Traits page.
  • AnyTrivialRegType is a metatype representing any Mojo type that's marked register passable.

You'll see them in signatures like this:

fn any_type_function[ValueType: AnyTrivialRegType](value: ValueType):
...
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?