Mojo 中的所有值都分配有相对应的数据类型,大多数类型都是由结构体定义的标称的类型。这些类型是标称的(或“命名的”),因为类型相等性是由类型的名称而不是其结构决定的。
有一些类型未定义为结构,例如下面的两种情况:
- 函数是根据其签名进行输入的。
- NoneType是一种具有一个实例(即None对象)的类型,用于表示“无值”。
Mojo附带了一个标准库,提供了许多有用的类型和实用函数。这些标准类型不是特权类型。每个标准库类型的定义都与用户定义类型一样,甚至包括 Int和 等基本类型String。但这些标准库类型是在大多数 Mojo 程序中使用的构建块。
最常见的类型是内置类型,它们始终可用且无需导入。这些类型包括数值(numeric)、字符串(strings)、布尔值(boolean)等类型。
标准库还包含许多可根据需要导入的类型,包括集合(collection)类型、用于与文件系统交互和获取系统信息的实用程序等。
Numeric types
它表示系统支持的最大尺寸的有符号整数——通常为 64 位或 32 位。
Mojo 还具有各种精度的整数和浮点值的内置类型:
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) |
表 1.具有特定精度的数字类型
表 1 中的类型实际上都是单一类型的别名, SIMD稍后将讨论。
所有数字类型都支持常见的数字和位运算符。math模块提供了许多附加数学函数。
您可能想知道何时使用Int以及何时使用其他整数类型。通常,Int当您需要整数类型并且不需要特定位宽时,这是一个不错的安全默认设置。将其用作IntAPI 的默认整数类型可使 API 更加一致和可预测。
Floating-point numbers
浮点类型表示实数。由于并非所有实数都能用有限位数表示,因此浮点数无法准确表示每个值。表 1 中列出的浮点类型Float64— 、Float32和 Float16— 遵循 IEEE 754-2008 标准来表示浮点值。每种类型都包含一个符号位、一组表示指数的位和另一组表示分数或尾数的位。表 2 显示了每种类型在内存中的表示方式。
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 |
表 2.浮点类型的详细信息
指数值为全 1 或全 0 的数字表示特殊值,允许浮点数表示无穷大、负无穷大、有符号零和非数字 (NaN)。有关如何表示数字的更多详细信息,请参阅Wikipedia 上的IEEE 754。
对于浮点值,有几点需要注意:
- 四舍五入误差,四舍五入可能会产生意外结果。例如,1/3 无法在这些浮点格式中准确表示。对浮点数执行的运算越多,四舍五入误差就越大。
- 连续数字之间的间距。连续数字之间的间距在浮点数格式的范围内是可变的。对于接近零的数字,连续数字之间的距离非常小。对于较大的正数和负数,连续数字之间的间距大于 1,因此可能无法表示连续的整数。
由于这些值是近似值,因此用相等运算符 ( ==) 进行比较很少有用。请注意以下示例:
var big_num = 1.0e16
var bigger_num = big_num+1.0
print(big_num == bigger_num)
运行结果为:True
较运算符(< >=等等)适用于浮点数。您还可以使用该math.isclose()函数比较两个浮点数在指定的公差范围内是否相等。
Numeric literals
除了这些数字类型之外,标准库还提供整数和浮点文字类型, IntLiteral以及 FloatLiteral。
这些文字类型在编译时用于表示代码中出现的文字数字。一般来说,您永远不应该自己实例化这些类型。
表 3 总结了可以用来表示数字的文字格式。
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.(浮点文字。必须包含小数点才能解释为浮点。) |
表 3.数字文字格式
在编译时,文字类型是任意精度(也称为无限精度)值,因此编译器可以执行编译时计算而不会出现溢出或舍入错误。
在运行时,这些值将转换为有限精度类型——Int对于整数值,Float64对于浮点值。(将只能在编译时存在的值转换为运行时值的过程称为具体化。)
以下代码示例显示了任意精度计算与运行时使用值进行的相同计算之间的差异Float64,后者存在舍入误差。
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
为了支持高性能数字处理,Mojo 使用类型 SIMD作为其数字类型的基础。SIMD(单指令、多数据)是一种处理器技术,允许您一次对整个操作数集执行操作。Mojo 的 SIMD类型抽象了 SIMD 操作。一个SIMD值表示一个 SIMD 向量— 即一个固定大小的值数组,可以放入处理器的寄存器中。SIMD 向量由两个参数定义 :
- 一个DType值,定义向量中的数据类型(例如,32 位浮点数)。
- 向量中元素的数量,必须是 2 的幂。
var vecr = SIMD[DType.float32, 4](3.0, 2.0, 2.0, 1.0)
对 SIMD 值的数学运算以元素为单位应用于向量中的每个单独元素。例如:
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
该SIMD模块定义了几种类型别名,它们是不同类型SIMD向量的简写。具体来说,Scalar类型只是一个 具有单个元素的向量。表 1SIMD中列出的数字类型 ,如和,实际上是不同类型标量值的类型别名:Int8Float32
alias Scalar = SIMD[size=1]
alias Int8 = Scalar[DType.int8]
alias Float32 = Scalar[DType.float32]
这乍一看可能有点令人困惑,但这意味着无论您处理的是单个Float32值还是 float32 值的向量,数学运算都会经过完全相同的代码路径。
The DType type
该DType结构描述了向量可以容纳的不同数据类型SIMD,并定义了许多用于操作这些数据类型的实用函数。该DType结构定义了一组别名,用作不同数据类型的标识符,例如DType.int8和DType.float32。声明向量时可以使用这些别名SIMD:
var v: SIMD[DType.float64, 16]
请注意,DType.float64不是类型,而是描述数据类型的值。您不能创建类型 的变量DType.float64。您可以创建类型SIMD[DType.float64, 1](或 Float64,它们是同一件事)的变量。
from utils.numerics import max_finite, min_finitedef 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 是浮点数:True float32 是积分:False float32 的最小/最大有限值 -3.4028234663852886e+38 3.4028234663852886e+38
标准库中还有其他几种数据类型也使用DType抽象。
Strings
Mojo 的String类型表示可变字符串。(对于 Python 程序员,请注意,这与 Python 的标准字符串不同,后者是不可变的。)字符串支持多种运算符和常用方法。
var s: String = "Testing"
s += " Mojo strings"
print(s)
执行结果如下图:
大多数标准库类型都符合该 Stringable特征,该特征表示可以转换为字符串的类型。用于str(value)显式将值转换为字符串:
var s = str("Items in list: ") + str(5)
print(s)
执行结果如下图:
String literals
与数字类型一样,标准库包含一个字符串文字类型,用于表示程序源中的文字字符串。字符串文字用单引号或双引号括起来。
相邻的文字连接在一起,因此您可以使用分成多行的一系列文字来定义一个长字符串:
var s = "A very long string which is ""broken into two literals for legibility."
要定义多行字符串,请将文字括在三个单引号或双引号中:
var s = """
Multi-line string literals let you
enter long blocks of text, including
newlines."""
请注意,三重双引号形式也用于 API 文档字符串。
IntLiteral与和不同FloatLiteral,StringLiteral不会自动实现为运行时类型。在某些情况下,您可能需要 使用内置 方法手动将StringLiteral值转换为。Stringstr()
例如,如果要将字符串文字连接到其他类型,则需要先转换StringLiteral为String值。这是因为许多类型可以隐式转换为String,但不能转换为StringLiteral。
# print("Strings play nicely with others: " + True)
# Error: ... right hand side cannot be converted from Bool to StringLiteral
print(str("Strings play nicely with others: ") + str(True))
运行结果是:
Strings play nicely with others: True
Booleans
Mojo 的Bool类型表示布尔值。它可以采用两个值之一, True或False。您可以使用运算符对布尔值取反not。
var conditionA = False
var conditionB: Bool
conditionB = not conditionA
print(conditionA, conditionB)
输出结果:
False True
许多类型都有布尔表示。任何实现该 Boolable特征的类型都有布尔表示。作为一般原则,如果集合包含任何元素,则计算结果为 True,如果集合为空,则计算结果为 False;如果字符串的长度不为零,则计算结果为 True。
Collection types
Mojo 标准库还包含一组基本集合类型,可用于构建更复杂的数据结构:
- List,一个动态大小的项目数组。
- Dict,一个键值对的关联数组。
- Set,无序的唯一项目集合。
- Optional 表示可能存在或不存在的值。
集合类型是泛型类型:虽然给定的集合只能保存特定类型的值(例如Int或),但你可以在编译时使用参数Float64指定类型。例如,你可以像这样创建一个值:ListInt
var l = List[Int](1, 2, 3, 4)
# l.append(3.14) # error: FloatLiteral cannot be converted to Int
您并不总是需要明确指定类型。如果 Mojo 可以推断类型,则可以省略它。例如,当您从一组整数文字构造列表时,Mojo 会创建一个List[Int]。
# Inferred type == Int
var l1 = List(1, 2, 3, 4)
如果您需要更灵活的集合,则该 Variant类型可以保存不同类型的值。例如,a可以在任何给定时间Variant[Int32, Float64]保存Int32 或值。(本节不介绍如何使用,有关更多信息,请参阅API 文档。)Float64Variant
以下部分对主要的收集类型进行简单介绍。
List
List是一个动态大小的元素数组。列表元素需要符合特征 CollectionElement,这意味着项目必须是可复制和可移动的。大多数常见的标准库原语,如Int、String和SIMD都符合此特征。您可以List通过将元素类型作为参数传递来创建一个,如下所示:
var l = List[String]()
该List类型支持 Python listAPI 的子集,包括附加到列表、从列表中弹出项目以及使用下标符号访问列表项的功能。
from collections import Listvar 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,
请注意,上面的代码示例在创建列表时省略了类型参数。由于列表是使用一组Int值创建的,因此 Mojo 可以 根据参数推断出类型。
使用时存在一些明显的限制List:
- 您当前无法从列表文字初始化列表,如下所示:
# Doesn't work!
var list: List[Int] = [2, 3, 5]
但您可以使用可变参数来实现同样的效果:
var list = List(2, 3, 5)
您不能print()列出列表,或将其直接转换为字符串。
# Does not work
print(list)
如上所示,只要它们属于同一Stringable类型,您就可以打印列表中的各个元素。
- 迭代 aList当前会返回 Reference每个项目的 a,而不是项目本身。您可以使用取消引用运算符访问该项目[]:
#: from collections import List
var list = List(2, 3, 4)
for item in list:print(item[], end=", ")
执行结果:
2, 3, 4,
但是,列表中的下标会直接返回该项目 - 无需取消引用:
#: 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
该Dict类型是一个保存键值对的关联数组。您可以Dict通过指定键类型和值类型作为参数来创建,如下所示:
var values = Dict[String, Float64]()
字典的键类型必须符合 KeyElement特征,值元素也必须符合 CollectionElement特征。
您可以插入和删除键值对、更新分配给键的值以及遍历字典中的键、值或项目。
迭代Dict器都产生引用,因此需要使用取消引用运算符,[]如下例所示:
from collections import Dictvar 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
该Set类型表示一组唯一值。您可以在集合中添加和删除元素、测试集合中是否存在某个值,以及执行集合代数运算,例如两个集合之间的并集和交集。
集合是通用的,并且元素类型必须符合 KeyElement特征。
from collections import Seti_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
表示Optional 可能存在也可能不存在的值。与其他集合类型一样,它是通用的,可以保存符合该 CollectionElement特征的任何类型。
# Two ways to initialize an Optional with a value
var opt1 = Optional(5)
var opt2: Optional[Int] = 5
# Two ways to initalize an Optional with no value
var opt3 = Optional[Int]()
var opt4: Optional[Int] = None
当 包含值时,Optional其计算结果为,否则为 。如果包含值,则可以使用 方法来检索对该值的引用。但是,在没有值的情况下调用会导致未定义的行为,因此您应该始终 在检查值是否存在的条件中保护对 的调用。TrueFalseOptionalvalue()value()Optionalvalue()
var opt: Optional[String] = str("Testing")
if opt:var value_ref = opt.value()print(value_ref)
输出:
Testing
或者,您可以使用该or_else()方法,如果存在则返回存储的值,否则返回用户指定的默认值:
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
在文档的多个地方,您会看到对可传递寄存器类型、仅传递内存类型和简单类型的引用。可传递寄存器类型和仅传递内存类型根据其保存数据的方式进行区分:
寄存器可传递类型完全由固定大小的数据类型组成,这些数据类型(理论上)可以存储在机器寄存器中。寄存器可传递类型可以包含其他类型,只要它们也是寄存器可传递的。 例如Int,、Bool和SIMD都是寄存器可传递类型。因此,寄存器可传递类型struct可以包含Int和Bool字段,但不能包含 String字段。寄存器可传递类型用 @register_passable装饰器声明。
寄存器可传递类型总是按值传递(即,复制值)。
仅内存类型由任何不符合寄存器可传递类型描述的类型组成。具体来说,这些类型通常具有指向动态分配内存的指针或引用。String、List和Dict都是仅内存类型的示例。
我们的长期目标是让用户清楚地了解这一区别,并确保所有 API 都适用于可传递寄存器类型和仅内存类型。但现在您会看到一些标准库类型仅适用于可传递寄存器类型或仅适用于仅内存类型。
除了这两个类别之外,Mojo 还有“简单”类型。从概念上讲,简单类型只是一种在其生命周期方法中不需要任何自定义逻辑的类型。组成简单类型实例的位可以被复制或移动,而无需知道它们的作用。目前,简单类型是使用装饰器声明的 @register_passable(trivial) 。简单类型不应仅限于寄存器可传递类型,因此未来我们打算将简单类型与装饰 器分开@register_passable。
AnyType and AnyTrivialRegType
在 Mojo API 中您会看到的另外两个东西是对AnyType和 的 引用AnyTrivialRegType。它们实际上是元类型,即类型的类型。
AnyType代表任意 Mojo 类型。Mojo 被视为AnyType一种特殊特质,您可以在 特质页面找到更多相关讨论。
AnyTrivialRegType是一个元类型,代表任何标记为寄存器可通过的 Mojo 类型。
您会在这样的签名中看到它们:
fn any_type_function[ValueType: AnyTrivialRegType](value: ValueType):...
您可以将其理解为具有一个类型为的 any_type_function参数,其中是可通过寄存器传递的类型,该类型在编译时确定。valueValueTypeValueType
标准库中仍然有一些类似的代码,但它正在逐渐迁移到更通用的代码,这些代码不区分可寄存器传递类型和仅内存类型。