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, theNone
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 name | Description |
---|---|
Int8 | 8-bit signed integer |
UInt8 | 8-bit unsigned integer |
Int16 | 16-bit signed integer |
UInt16 | 16-bit unsigned integer |
Int32 | 32-bit signed integer |
UInt32 | 32-bit unsigned integer |
Int64 | 64-bit signed integer |
UInt64 | 64-bit unsigned integer |
Float16 | 16-bit floating point number (IEEE 754-2008 binary16) |
Float32 | 32-bit floating point number (IEEE 754-2008 binary32) |
Float64 | 64-bit floating point number (IEEE 754-2008 binary64) |
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 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 |
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)
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.
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, 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)
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)
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]()
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)
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)
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)
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)
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)
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=", ")
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 aReference
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=", ")
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=", ")
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)
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[])
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)
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"))
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
, andSIMD
, for example, are all register-passable types. So a register-passablestruct
could includeInt
andBool
fields, but not aString
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
, andDict
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 treatsAnyType
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?
Thank you! We'll create more content like this.
Thank you for helping us improve!