亮点 {#1-0-0-highlights}
在 Mojo 1.0 中,我们开始为 Mojo 语言和标准库定义稳定性策略。在 1.x 周期内,变更应以增量变更为主。我们仍可能进行破坏性变更,但会谨慎管理。
目前,大多数核心语言特性已稳定,这意味着它们不会被移除或以破坏源码兼容性的方式更改。我们也已开始将标准库 API 标记为稳定,首批特意选定的范围很小,将在后续版本中逐步扩充。详情请参见 Mojo 稳定性保证。
我们为 1.0 努力打磨了命名、默认值和安全边界。这确实意味着本版本的破坏性变更比平常更多。但几乎每个破坏性变更都附带一个弃用别名和一个编译器 fix-it,因此迁移工作是机械式的。
语言易用性:Mojo 新增了 lambda 表达式——匿名、单表达式闭包,会被脱糖为嵌套的 def。列表表达式现在默认构造 Array 而非 List,关键字可变参数(keyword variadics)可使用 Python 风格的 ** 语法转发给另一个函数。参见语言增强。
内存安全与指针统一:Pointer 和 UnsafePointer 被统一为单一的 Pointer 类型,不安全性现在标记在单个操作上,而非整个类型上。相应地,意外获取 UnsafeAnyOrigin 变得更难:到它的隐式扩宽已被弃用,且结构体字段不能再隐藏一个 UnsafeAnyOrigin。参见指针与内存。
生命周期检查器现已理解容器内部:一项名为内部源(interior origins)的新实验性特性允许 List、Dict、String 等多种类型返回绑定到内部源的元素引用,因此在变更(mutation)期间持有元素引用的代码会被拒绝,而不是在重新分配后悄悄变成悬垂引用。参见语言增强和集合与迭代器。
显式优于隐式:许多推断规则和静默默认值现在必须显式写出。不带 var 的变量声明已被弃用,方法的 self 必须具有 Self 类型,裸 **kwargs 必须写作 var **kwargs。import 体系已被重构,使名称解析显式且一致。参见语言变更。
每个概念只有一个名字、一个类型:本版本整合了之前漂移的词汇。size 全面变为 length,InlineArray 变为 Array,StringSlice 变为 StringSpan,ImplicitlyDestructible 变为 Deinitable(析构器写作 __deinit__()),read 变为 imm。重复的类型也被整合:Int 现在是 Scalar[DType.int] 的别名,基于 Int 和基于 Scalar 的 range() 类型被统一为单一一个 dtype 参数化家族。参见库变更和 SIMD 与数值类型。
集合可接受更多类型:建模为独占资源的类型现在可以放入容器中。若干集合类型仅在其元素类型满足时才遵循 Deinitable,因此显式销毁元素的集合现在能编译通过(此前不能编译);这样的集合必须用新的 deinit_with() 方法排空。Optional 和 Variant 也接受非 Movable 的元素类型。参见显式销毁类型与去初始化。
约束承担更多工作:where 子句可接受一个字符串字面量消息,当约束失败时编译器会报告该消息;TypeList.all_conforms_to() 精化参数包的每个元素;类型相等现在用 == 和 != 表示。结构体类型默认为 Movable,可用 Movable where <cond> 来收窄遵循。参见语言增强和类型系统与 trait。
默认即正确,即便牺牲便利性:原本静默执行看似合理但其实是错误行为的 API,现在会拒绝或直接做正确的事。用无效连续切片索引会退出程序,而不是悄悄地截断或环绕。遍历字符串默认产出字形簇(grapheme clusters),所以 for c in my_string 会产出用户在屏幕上感知为单个字符的内容。range() 拒绝非数值元素类型以及过去会无限循环的浮点形式,size_of() 返回分配大小,修复了 List 中过度对齐类型的内存损坏。参见集合与迭代器、字符串与文本和 SIMD 与数值类型。
更快的 Python 互操作:PythonObject 的算术、比较和成员运算符现在通过 CPython 的抽象协议进行分发,而不是先做 Python 级别的属性查找再做绑定方法调用。这使互操作热路径大约快 12 倍,并更贴合标准 Python 运算符语义。参见 Python 互操作。
Mojo 与 MAX 之间更清晰的边界:一些与加速器编程相关的标准库 API 已迁移到新的 max Mojo 包,layout 包现在与 MAX 一起分发而非 Mojo。相应地,Int 和 UInt 不再遵循 DevicePassable,也不能再传入 GPU 内核:它们是平台大小的索引类型,当主机与设备对宽度看法不一致时传入加速器会导致误编译。请改用 Int32 等定宽类型。参见包迁移和 MAX 26.5 变更日志。
文档 {#1-0-0-documentation}
语言增强 {#1-0-0-language-enhancements}
支持 lambda 表达式:匿名、单表达式闭包,会被脱糖为嵌套的 def。与 Python 一样,函数体是单个表达式且没有 return;与 Python 不同的是,参数使用括号包裹并按 def 签名方式书写类型——例如 lambda (x: Int) {} -> Int: x + 1。你可以省略捕获列表 {…} 与返回类型:省略捕获列表时会对函数体中的自由变量进行 imm 捕获(无自由变量时为 thin),省略返回类型时默认为 None——因此当 expr 类型为 None 时,裸 lambda: expr 是合法的。这些都是固定默认值,而非推断(非 None 的函数体仍需显式 -> T)。
一个 thin(无捕获)的 lambda 是一个函数值,与按名引用的 def 完全相同;任何其他 lambda 都是闭包实例,一个没有函数类型的运行时值。完整规则请参见 lambda 表达式参考页。
Mojo 支持一项名为内部源(interior origins)的新实验性特性,允许集合防范一类常见的内存安全问题。例如 List 现在返回绑定到列表内部源而非整个列表源的元素引用,因此变更列表(例如用 append() 或 pop())会使元素引用失效。生命周期检查器现在会正确拒绝在变更期间持有元素引用的代码,而不是让其在重新分配后悄悄变成悬垂引用:
var list = [1, 2, 3]
ref elem = list[0]
list.append(4) # may reallocate, so `elem` in invalidated
print(elem) # error: use of invalidated interior reference
Mojo 现在会选用 Array(而非 List)作为列表表达式构造的默认类型,消除了隐式堆分配。例如:
var x = [1, 2, 3]
# type_of(x) = Array[Int, 3]
Mojo 现在还支持从字面量初始化器进行类型推断:
var x: List[_] = [1, 2, 3]
var y: List = [1.0, 2.0, 3.0]
结构体类型现在默认为 Movable。要退出常开的可移动性,可显式指定带条件的 Movable 遵循(使用 Movable where <cond>),或使用 Movable where False 完全退出 Movable 遵循。
现在可使用 Python 风格的 ** 语法将关键字可变参数转发给另一个接受关键字可变参数的函数:
def takes_them(var **kwargs: Int): ...
def pass_them(var **kwargs: Int):
takes_them(**kwargs^)
现在可以直接调用具有未绑定类型参数的动态函数指针;编译器会从调用参数推断参数,并在间接调用之前对被调用方进行特化。这仅适用于被特化为单个值的参数,特别地,它使运行时函数调用上的源参数成为可能:
var fp: def[a: ImmOrigin](ref [a] x: Int) thin -> None
fp(42)
Mojo 现在支持 == 和 != 用于类型相等检查。
where 子句现在可接受一个可选的字符串字面量消息,写作 where (condition, "message")。当约束失败时编译器会将该消息包含在诊断中,并支持在允许 where 子句的所有位置使用:函数/结构体的尾随约束、结构体条件遵循子句,以及 alias/comptime 声明。
def foo[sc: Int]() where (sc > 1, "scaling factor must be greater than 1"):
...
TypeList.all_conforms_to() 约束现在保留与直接 conforms_to() 约束相同的证明结构,因此编译器可将其用作条件遵循检查与类型精化的证据。条件遵循因此可以依赖整个类型参数包的 trait 层级关系:给定一个继承自 Serializable 的 trait JsonSerializable,条件遵循类型不再需要重复继承的条件。相同的约束也精化 where、comptime assert 与 comptime if 上下文中可变类型参数包的每个元素:
def write_all[*Ts: Movable](mut writer: Some[Writer], *args: *Ts):
comptime if Ts.all_conforms_to[Writable]():
comptime for i in range(args.__len__()):
# previously required rebinds to refine types, now just works
args[i].write_to(writer)
Mojo 现在为 TypeList.of 推断 Trait,因此:
comptime TL = TypeList.of[Int, Bool]
# works without
comptime TL = TypeList.of[Trait = AnyType, Int, Bool]
Mojo 现在会对冗余的 trait 组合发出警告:
# Warning: Redundant trait composition: 'Copyable' already implies 'AnyType'
comptime T : AnyType & Copyable = xxx
Mojo 改进了对 import 位置的跟踪,现在会显示包含诊断的包是何时首次引入程序的:
Included from /bug.mojo:2:
Included from /foo/__init__.mojo:3:
/foo/nested_pkg/my_module.mojo:1:5: note: candidate not viable: unexpected argument
对于预编译包(.mojoc 文件),编译器会省略包内部的位置。它也不会报告 std 包是何时被引入的,因为它们会被隐式 import 到每个模块中。
语言变更 {#1-0-0-language-changes}
所有变量声明都应使用 var。隐式变量声明已被弃用,现在会发出一个插入 var 的 fix-it 警告:
x = 0 # implicit declaration of 'x' is deprecated; add 'var' before the name
var x = 0 # Fixed; no warning
每个对名字的首次赋值都会发出警告,:= 海象运算符目标和裸 x: T 注解也包括在内。已经显式声明其绑定方式的绑定形式不受影响:for 目标、with ... as、except ... as、推导式目标,以及 _ 丢弃。
析构器双下划线方法现在应写作 __deinit__(),以与 __init__() 在命名上保持一致。旧的 __del__() 写法仍可用,但现在会发出弃用警告并附带将其重命名的 fix-it。
import 体系已被重构,使名称解析显式且一致:
import 解析现在在目录内遵循一致的优先顺序:源码包,然后是预编译的 .mojoc 文件,再是源码模块,最后是旧式预编译的 .mojopkg 文件。此前的顺序未明确。
相对 import 必须使用 from(from . import foo);import .foo 形式不再可用。
绝对 import import a.b.c 现在会将 a、a.b、a.b.c 全部绑定到作用域中,此前只有 a.b.c 可用。还修复了两个相关 bug:先 import a 后 import a.b 不再报 "invalid redefinition of 'a'" 错误;函数作用域内的点分 import(函数体中的 import a.b)现在可用。
被导入包的子模块现在仅在包的 __init__.mojo 重新导出它们(例如用 from . import sub)时才可访问。子模块的绝对 import(import pkg.submodule)始终可用,绕过 __init__.mojo。
不带显式 import 的包内访问已被弃用,将在未来版本中移除。一个模块现在必须用 from . import foo 显式 import 在其所在包中其他地方定义的符号。
现在可以通过普通(非包)目录使用相同的路径式语法 import 模块和包,例如 import dir.nested_dir.module。一个解析为目录的 import 语句本身不能用于作用域查找(import dir 之后用 dir.nested_dir.module.foo() 是错误)。
独立模块不能再 import 自己的名字(例如 util.mojo 内的 import util),因为这只能遮蔽搜索路径上同名包。包内模块不受影响。
从不同模块 import 同名函数并将它们合并为一个重载集,现已被弃用并发出警告;未来版本将拒绝第二个 import。请改为从单个模块 import 该名字。
通配符 import 现在按文本顺序后解析优先:最后导入的声明会遮蔽较早的声明,包括从 std.prelude 隐式导入的那些。
编译器现在在每个 import 站点报告失败的 import 诊断,而不是每个模块仅报告一次。
imm 现在是 read 参数与闭包捕获约定的首选写法。read 仍可用但即将被弃用。
size_of() 现在返回分配大小:即存储大小向上取整到该类型对齐后的值,也就是该类型数组中相邻元素之间的步长。这仅对存储大小不是其对齐整数倍类型(例如 @align(N) 超过其自然对齐的结构体)会改变结果,并修复了当此类类型用于 List 及其他按 count * size_of 字节复制的集合时的内存损坏问题。
@explicit_destroy 装饰器不再让结构体退出 Deinitable 遵循。请改用 Deinitable where False:
struct ExplicitDestroy(Deinitable where False):
def destroy(deinit self):
pass
也可以使用非平凡条件使类型有条件地被显式销毁,例如 Deinitable where conforms_to(T, Deinitable)。
使用 @explicit_destroy("custom error") 可在实例不能被隐式删除时给用户额外提示。不带错误字符串参数使用 @explicit_destroy 现在会报错。
旧式闭包,以及用于声明它们的 @parameter 和 @__copy_constructor 装饰器,已被弃用,不应在新代码中使用。请改用较新的带捕获列表的闭包语法。使用这些装饰器目前不会产生警告,因为仍有少量 API 在使用这些旧式闭包。
用户编写的结构体现在必须在继承列表中显式声明闭包 trait 遵循,以满足 def(...) -> ... 闭包 trait。此前 Mojo 会隐式接受具有兼容 __call__() 的结构体(鸭子类型)。在结构体继承列表中声明该 trait:
struct Double(def(Int) -> Int): # previously: `struct Double:`
def __call__(self, x: Int) capturing -> Int:
return x * 2
Mojo 在结构体定义处检查遵循,而不是推迟到使用点。
裸 **kwargs 现在会报错;在函数声明和函数类型中均应写作 var **kwargs(fix-it 会插入它)。var 此前已是唯一受支持的约定——是参数默认为 imm 的唯一例外,此前是静默应用的——所以语义未变。
参数列表内的 where 子句(例如 [x: Int where x > 0])在经过一段弃用后不再被支持。请在签名后使用尾随 where 子句,例如 def foo[x: Int]() where x > 0:。
结构体字段不能再隐藏 UnsafeAnyOrigin——像 var ptr: Pointer[Int, MutUnsafeAnyOrigin] 这样的字段现在会报错。Mojo 无法判断所属结构体的使用中是否包含 UnsafeAnyOrigin,因此不会对其上下文中的值做生命周期扩展。典型解决方案是添加一个 Origin 参数,但如果你显式管理底层数据的生命周期,也可以使用 UntrackedOrigin:
struct Example[origin: Origin]:
var ptr: Pointer[Int, Self.origin]
方法的 self 参数现在必须具有 Self 类型;请将自定义的 self 类型改为 where 子句。
struct Foo[T: AnyType]:
# ERROR: def foo(self: Foo[Int]):
def foo(self) where Self.T == Int:
...
Mojo 现在拒绝仅参数约定不同(imm 与 mut)的函数重载,因为编译器不允许基于此解析重载。
不能再使用预定义和保留字(例如 class、del、match、yield)作为自由函数的名字。现在在声明处就会报错,而不是静默生成一个永远无法被调用的函数。
编译器现在拒绝在特定语句中间出现换行(此前是允许的):
在 def/struct/trait/comptime 关键字与紧随其后的标识符之间。
在函数定义的 async 与 def 关键字之间。
在 import 语句中间的任何位置,括号包裹的 import 列表除外。
库稳定化 {#1-0-0-library-stabilizations}
我们已开始稳定 Mojo 标准库的过程。标记为稳定的 API 不会被移除或以破坏源码兼容性的方式更改。
我们谨慎地仅稳定我们能承诺的 API,因此首批稳定 API 集合很小,但我们将在后续版本中持续补充。
以下标准库类型在本版本中拥有一个或多个稳定 API:
稳定状态在 API 参考中按 API 标注。更多信息,请参见 Mojo 稳定性保证。
库变更 {#1-0-0-library-changes}
类型系统与 trait {#1-0-0-type-system}
ImplicitlyDestructible 已被重命名为 Deinitable,以与 deinit 参数约定和析构器的 __deinit__() 写法保持一致。ImplicitlyDestructible 和中间过渡的 ImplicitlyDeletable 写法仍作为弃用别名保留。
反射成员 Reflected.field_type[name] 已被重命名为 Reflected.field[name],因为它返回的是该字段的一个可链式 Reflected 句柄,而非字段的裸类型,旧名称并不准确。请从句柄的 .T 成员获取字段类型,例如 reflect[T].field["x"].T。同时还新增了一个按索引的对应物 reflect[T].field_at[idx],使在按索引遍历字段时能恢复字段的具体类型(此时名字不能作为字面量提供)。
std.utils.type_functions 中的 ConditionalType 类型函数已被弃用。请改用等价的三元表达式:comptime Storage = Int if cond else NoneType。
Error 现在是 ImplicitlyCopyable,因此用裸 raise e 重新抛出捕获的错误不再需要转移标记。捕获的 StackTrace 现在是引用计数的,所以拷贝一个 Error 仅需一次引用计数自增,而不是复制整个栈跟踪。raise e^ 仍然可用并避免拷贝。
Equatable trait 现在允许仅位置参数实现,实现者上的参数不再需要与 trait 完全匹配。
指针与内存 {#1-0-0-pointer-memory}
Pointer 和 UnsafePointer 类型已被统一。新的统一 Pointer 类型包含 UnsafePointer 的功能,不安全操作以 unsafe_ 为前缀或要求 unsafe_ 前缀的关键字参数。UnsafePointer 名称和未加前缀的不安全操作已被弃用。
两种指针类型共享相同布局并可隐式转换,因此大多数代码不受影响。整个标准库的裸指针 API 现在都使用统一的 Pointer。
统一前的指针别名已被弃用,请改用 Pointer 家族,Pointer 的 type 参数被重命名为 T:
| 旧写法 | 新写法 |
|--------------------------------------|------------------|
| UnsafePointer | Pointer |
| MutUnsafePointer | MutPointer |
| ImmUnsafePointer, ImmutUnsafePointer | ImmPointer |
| OptionalUnsafePointer | OptionalPointer|
调用单独不安全的指针操作——未检查边界、别名转换、移动或覆盖内存——的代码应改用 unsafe_* 写法,如下所示:
<!-- rumdl-disable MD033 -->
| 旧写法 | 新写法 |
|------------------------------|-----------------------------------|
| [i] | [unsafe_offset=i] |
| ptr + i | unsafe_offset(i) |
| load() | unsafe_load() |
| store() | unsafe_store() |
| strided_load() | unsafe_strided_load() |
| strided_store() | unsafe_strided_store() |
| gather() | unsafe_gather() |
| scatter() | unsafe_scatter() |
| as_noalias() | unsafe_as_noalias() |
| address_space_cast() | unsafe_address_space_cast() |
| mut_cast() | unsafe_mut_cast() |
| take_pointee() | unsafe_take_pointee() |
| init_pointee_move() | unsafe_write(value^) |
| init_pointee_copy() | unsafe_write(copy=value) |
| destroy_pointee() | unsafe_deinit_pointee() |
| destroy_pointee_with() | unsafe_deinit_pointee(closure) |
| init_pointee_move_from() | unsafe_write_move_from(src) |
| free() | unsafe_free()<sup>*</sup> |
<sup>*</sup> 内存分配应迁移到感知 layout 的 memory.alloc 包。
<!-- rumdl-enable MD033 -->
此前未加前缀的名字仍可用,但现在会从生成的文档中隐藏,并在被调用时发出弃用警告。每个方法的 docstring 都记录了调用方必须遵守的精确 Safety: 要求。
诸如 memset 等裸内存函数也已被重命名加上 unsafe_ 以明确其不安全性。旧名称已被弃用,将在未来版本中移除。
静默将 UnsafePointer 的源扩宽为 UnsafeAnyOrigin 的隐式转换正在被移除。UnsafeAnyOrigin 是一个不安全逃生舱,会静默扩展不相关的生命周期并禁用排他性检查,因此绝不应被隐式应用。将 UnsafePointer 转换为 MutUnsafeAnyOrigin 或 ImmUnsafeAnyOrigin 的构造器现已弃用,将 UnsafePointer 转换为 Optional[UnsafePointer[..., UnsafeAnyOrigin]] 的构造器已被移除。优先保留具体源;如果必须丢弃,请用 as_unsafe_any_origin() 显式转换。
由于源现在被保留,可能需要两处调用点更新。当参数的源是一个真正固定的 MutAnyOrigin/ImmutAnyOrigin(通常是 C-FFI 签名)时,传入具体指针现在需要显式 as_unsafe_any_origin()。排他性检查现在也适用于 unsafe_memcpy()(及类似)调用中 dest 与 src 来自同一缓冲区的情况,因此此前能编译的缓冲区内拷贝现在会报错。可通过将一个参数变为 unsafe any-origin 来退出——dest 与 src 不重叠本就是 unsafe_memcpy() 的前置条件:
unsafe_memcpy(
dest=buf + dst_off,
src=(buf + src_off).as_unsafe_any_origin(),
count=n,
)
进一步的重命名,旧名称作为弃用别名保留:
| 旧写法 | 新写法 |
|-------------------------------------|-----------------------------|
| as_immutable(), get_immutable() | as_imm() |
| OwnedPointer.take() | OwnedPointer.into_inner() |
| StaticConstantOrigin | ImmStaticOrigin |
as_imm() 的重命名涵盖 Pointer、Span 和 StringSpan,它们此前将同一操作以两种不同方式拼写。
AddressSpace 已从 std.memory.pointer 移至 std.memory.address_space。
OwnedPointer.steal_data()、ArcPointer.steal_data() 和 List.steal_data() 已被重命名为 unsafe_take_allocation(),并返回一个拥有所有权的 Allocation 而非裸指针。这些方法保留 unsafe_ 前缀,因为元素被交接时仍处于已初始化状态:释放不会运行其析构器。请用 unsafe_leak() 恢复此前的裸指针。List.steal_data() 和 OwnedPointer.steal_data() 仍作为 @deprecated 方法保留,而 ArcPointer.steal_data() 被直接移除:重构用的 ArcPointer(unsafe_from_raw_pointer=...) 构造器现在接收指向控制块的指针(从 unsafe_take_allocation().unsafe_leak() 获取),不再接受 steal_data() 此前交出的载荷指针。
Pointer 现在支持通过新的 offset_from() 方法将两个指针相减,以按指针指向类型的元素为单位计算它们之间的有符号距离;- 运算符做相同的事。与其他产生新指针并保持在不安全指针类型背后的指针算术运算符不同,两个指针相减返回一个 Int 距离,并且在安全指针上也可用。
集合与迭代器 {#1-0-0-collections}
InlineArray 已被重命名为 Array,第一个参数从 ElementType 改为 T,第二个参数从 size 改为 length。一个临时的 InlineArray comptime 别名用于过渡,.size 仍作为 .length 的弃用别名保留。请将显式的 InlineArray[ElementType=..., size=N] 用法更新为 Array[T=..., length=N]。
Array 不再遵循 ImplicitlyCopyable,因为它本质上复制成本不低。它仍遵循 Copyable。
Array 不再是 Defaultable。此前它遵循 Defaultable,但实际尝试默认构造一个 Array 会编译失败。
List、Span、String 和 StringSpan(前身为 StringSlice)使用连续(非步进)切片索引时,对于无效切片现在会退出程序,而不是静默截断。要使切片合法:
start 和 end 必须各自在 0 到 container_length - 1 的范围内。
start 必须小于或等于 end。
这里的 container_length 对 List/Span 是 len(container),对 byte= 索引的字符串是 string.byte_length(),对 codepoint= 索引的字符串是 string.count_codepoints(),对 grapheme= 索引的字符串是 string.count_graphemes()。
var lst: List = [1, 2, 3]
lst[0:100] # previously clamped to `lst[0:3]`; now aborts
lst[3:1] # previously returned an ill-defined result; now aborts
lst[:-1] # previously wrapped to `lst[0:2]`; now aborts
对负索引的支持已在 v1.0.0b1 中移除。常见的 "除最后一个元素外的所有元素" 惯用法现在必须显式写出结束索引。注意 lst[0 : len(lst) - 1] 在空 lst 上仍会中止(0 : -1),因此如果 lst 可能为空,请使用 lst[: max(len(lst) - 1, 0)]。
多种类型已采用内部源(见上文语言增强中的描述),包括 List、Deque、Variant、String、Dict、LinkedList、OwnedPointer 和 HostBuffer。对这些容器的引用或视图现在携带一个内部源,因此生命周期检查器会拒绝在变更期间持有的引用,而不是让其在重新分配后悄悄变成悬垂引用。
List.insert() 和 LinkedList.insert() 不再规范化负索引。Mojo 集合正逐步摒弃负索引,因此有效索引范围现在是 [0, len(self)];负索引越界并中止(在启用断言时检查)。
List.capacity 现在是 capacity() 方法而非公共字段。这使已分配容量脱离稳定的公共字段面,因为它应仅通过 append() 等操作间接改变。请将 my_list.capacity 替换为 my_list.capacity()。
新增 List.try_index(),返回某个值在列表中的索引(如果存在)而不抛异常,并且与 comptime 兼容。
MutSpan 和 ImmSpan 别名现在从 prelude 导出,因此不再需要从 std.collections 显式 import。这与 prelude 已导出的 Pointer 的 Mut/Imm 别名一致。
Span 现在拥有一个仅关键字参数 address_space(默认为 AddressSpace.GENERIC),因此 span 可以查看非默认地址空间中的内存,例如 GPU 共享内存。仅地址操作(索引、切片、unsafe_ptr()、as_imm() 和 SIMD 搜索辅助)在任何地址空间都能工作,并在结果中保留地址空间;当元素类型可寄存器传递时,fill() 也是如此。其余涉及元素拷贝的操作(迭代、copy_from()、哈希、相等和写入)仍限于默认地址空间。
Span 的指针加长度构造器参数已从 ptr 重命名为 unsafe_ptr,以标记此构造路径是内存不安全的:调用方必须确保指针指向至少 length 个有效元素。请将 Span(ptr=..., length=...) 更新为 Span(unsafe_ptr=..., length=...)。
Span 已从 std.memory.span 移至 std.collections.span。
新增 Dict.insert(key, value) 和 Dict.clear_with(destroy_func),并相应提供 Set.insert(element) 和 Set.clear_with(destroy_func),使键、值或元素类型不是 Deinitable 的 Dict 或 Set 也能被填充和清空。与 dict[key] = value 不同,insert() 不会销毁被替换的条目:它将其移动出来,并作为 Optional 返回给调用方销毁。clear_with() 将每个条目交给 destroy_func 并保留容量。
Dict.fromkeys(keys, value) 已从接受 List 泛化为接受任意可迭代的键。两种形式都要求键和值类型为 Deinitable。
按引用的 Dict 迭代(for entry in dict、keys()、values()、items() 和 reversed())不再要求键和值类型为 Deinitable。这些迭代器只借用引用、从不销毁条目,因此它们现在能在键或值类型不是 Deinitable 的 Dict 上工作。消费式迭代(for entry in dict^ 和 take_items())仍要求 Deinitable,因为它会丢弃未 yield 的条目。
Variant.take[T]() 和 Variant.unsafe_take[T]() 已被重命名为 Variant.unwrap[T]() 和 Variant.unsafe_unwrap[T]()。旧名称作为 @deprecated 方法保留,将在未来版本中移除。
Optional 不再遵循 Iterator;它现在是一个包含 0 或 1 个元素的 Iterable 集合。for value in opt 和 for value in opt^ 不变,但直接将 Optional 用作迭代器的代码(例如对其调用 next())不再编译,应改为对 Optional 进行迭代。
BitSet 新增 test_range[bit_value: Bool, *, lo: Int, hi: Int],可高效测试一个位范围是否持有期望值,以及大小调整构造器:resized_from: 关键字构造器从更小的集合零扩展(并在缩小时 debug 断言没有设置位被丢弃),而一个接受 truncate_set_bits: () 关键字参数的伴随重载则执行截断。
新增 Tuple.consume_elements(),将每个元素从元组中逐一移动到调用方提供的闭包中。像 a, b = t^ 这样的解构会拷贝每个元素,因此无法拆解元素为 Movable 但非 ImplicitlyCopyable 的元组;consume_elements() 转移所有权,与 VariadicPack.consume_elements() 对应。
var t = ([1, 2, 3], [4, 5, 6]) # `List` is not `ImplicitlyCopyable`
t^.consume_elements[handler]()
显式销毁类型与去初始化 {#1-0-0-linear-types}
若干集合类型现在有条件地遵循 Deinitable,仅在其元素类型遵循时才遵循。这使得集合能持有非 Deinitable 元素(此前这样的集合会编译失败);非 deinitable 元素的集合本身被显式销毁,必须用新的 deinit_with() 方法排空,该方法对每个元素调用一个闭包:
collection^.deinit_with(my_destroy_closure)
对于 Deinitable 元素类型——常见情况——这些都不受影响,但按值接受这些集合之一的泛型代码现在可能需要在其元素约束中加入 & Deinitable(例如 def foo[T: Movable & Deinitable, //](var arr: Array[T, 3]):),以便集合能被销毁。
消费式迭代始终要求 Deinitable,因为它会丢弃未 yield 的元素。除此之外,受影响类型在一个线性(linear)元素仍会排除什么上各有不同:
| 类型 | 仍要求 Deinitable 元素的操作 |
|-------------------|--------------------------------------------------------------------------------------------------------------------------------------|
| Array | 无。 |
| Tuple | 无,但请用 deinit_with() 或 consume_elements() 拆解一个线性元组。 |
| Deque | 销毁元素的操作:append()、extend()、insert()、clear()、remove() 等。 |
| Dict | 销毁元素和键/值拷贝的操作:__setitem__()、setdefault()、fromkeys()、update()、pop()、clear()。 |
| Set | 修改元素的操作:add()、remove()、discard()、clear()。 |
| LinkedList | 仅 clear()。 |
| OwnedPointer[T] | 仅当 T 遵循时才遵循;用 into_inner() 或 unsafe_take_allocation() 消费它。 |
因此一个线性 Dict 或 Set 可以被构造和拆解但不能被填充,而线性 LinkedList 还能被填充。Set 的元素约束也从 KeyElement & Deinitable 放宽为仅 KeyElement,且 LinkedList.insert() 不再对越界索引抛异常;与 List.insert() 一样,现在会中止(在启用断言时检查)。
消费式迭代通过 IterableOwned 遵循进行条件化;以 IterableOwned 为约束的泛型代码现在会在约束处拒绝非遵循的元素类型,而不是稍后在 __iter__() 内部失败。
Array 的元素类型约束从 Movable 放宽为 AnyType,因此 Array 现在可以持有非 Movable 元素类型。Movable 遵循现在以元素为条件:移动构造(包括像 [a, b, c] 这样的列表字面量构造)要求 Movable 元素,而索引、按引用迭代和析构则不要求。使用 Movable 元素类型的代码不受影响,因为 Movable 元素仍产生可移动数组。
Optional 和 Variant 现在接受非 Movable 的元素类型。它们的元素类型现在以 AnyType 为约束,Movable、Copyable 等遵循以元素类型为条件。非 Movable 值可用新的基于闭包的 init_with= 构造器和 Variant.set() 重载就地存储,这些构造器直接将值就地构造到存储中(placement-new)而非移动它。两种类型上的 deinit_with() 也不再要求 Movable,因此既不是 Movable 也不是 Deinitable 的元素类型完全可用:
var opt = Optional[Pinned](call=make) # construct in place
var v = Variant[Pinned, Int](call=make)
v.set(call=make) # replace in place
变长 **kwargs 的容器底层已从 OwnedKwargsDict 重命名为 StringDict。StringDict 不再要求其值类型 V 为 Deinitable。值为线性(非 Deinitable)的关键字字典本身也是线性的,必须用新的 deinit_with(deinit_func) 显式拆解,该方法将每个键和值交给 deinit_func。它还新增了 insert(key, value)(不销毁被替换条目,将其作为 Optional[DictEntry] 返回)和 popitem()(移动并返回整个条目),与 Dict 对应。就地销毁被替换值的操作——kwargs[key] = value 和两参数的 pop(key, default)——仍要求 V 为 Deinitable;对于线性值,请使用 insert()、popitem() 或单参数的 pop(key)。
现在可以在元素类型不是 Copyable 时对 List、Dict、Array、LinkedList 和 Set 进行 owned 元素迭代:这些集合上的 IterableOwned 遵循现在仅要求 Movable & Deinitable,去掉了 Copyable。因此 for var x in list^: 循环对非 Copyable 元素类型也能工作。
Optional 新增 deinit_assert_empty(),可在没有调用方提供的去初始化器的情况下销毁一个空的线性 Optional,并在安全断言构建中如果其非空则中止。Optional.map() 和 Optional.and_then() 现在也在元素类型为线性(非 Deinitable)时工作:它们将包含的值移出并显式销毁空出的 Optional,因此线性值可以被转换并交还给调用方。
is_trivially_destructible() 已被重命名为 is_trivially_deletable()。它现在接受任何类型(T: AnyType)而不要求 T: Deinitable,对非 Deinitable(线性)类型返回 False。
字符串与文本 {#1-0-0-string}
StringSlice 已被重命名为 StringSpan,与 Span 等其他非拥有视图类型一致。StringSlice 暂时仍作为 comptime 别名保留,以缓解向新名称的过渡。
StringSpan 拥有 MutStringSpan 和 ImmStringSpan 别名,与已为 Span 和 Pointer 提供的 Mut/Imm 别名一致。此前的 MutStringSlice 和 ImmStringSlice 名称仍作为兼容别名保留。
遍历 String、StringSpan 或 StringLiteral 现在默认产出字形簇。它们的 __iter__() 和 __reversed__() 方法返回一个 GraphemeSliceIter,因此 for c in my_string: 会产出用户在屏幕上感知为单个 "字符" 的内容。当你需要更低层视图时仍可用:codepoints() 或 codepoint_slices() 用于 Unicode 标量,bytes() 用于原始 UTF-8 字节。
SIMD 与数值类型 {#1-0-0-simd-numeric}
Int 现在是 Scalar[DType.int] 的别名,整数字面量会物化为该 Scalar 类型。因此,一些转换变得更严格。
新增 SIMDLength 类型用于 SIMD 向量的长度。在从 SIMD 参数推断参数时使用它,例如 def frob[w: SIMDLength](v: SIMD[DType.int, w]): ...;将长度保持未绑定(SIMD[DType.int, _])以对任何 SIMD 类型参数化,在其他所有情况下使用 Int。该类型在 nightly 版本中曾短暂名为 SIMDSize;SIMDSize 仍作为弃用别名保留。
SIMD 的第二个参数已从 size 重命名为 length,以与其声明时使用的 SIMDLength 类型以及库中其他地方用于元素计数的 length 词汇保持一致。位置用法如 SIMD[DType.float32, 4] 不受影响,按 v.size 读取参数仍可用但会发出警告。按关键字绑定为 SIMD[dtype, size=4] 是错误,必须更新为 length=。
同样将 length 标准化取代 size 的情况也适用于其他地方。在适用的地方,旧名称作为弃用别名保留:
| 旧写法 | 新写法 |
|---------------------------------|-----------------------------------|
| ComplexSIMD[..., size=N] | ComplexSIMD[..., length=N] |
| TypeList.size | TypeList.length |
| DeviceContextList[size=N] | DeviceContextArray[length=N] |
| List.resize(new_size=, value=)| List.resize(new_length=, fill=) |
| List.shrink(new_size=) | List.shrink(new_length=) |
range() 已被重做:
既然 Int 就是 Scalar[DType.int],基于 Int 和基于 Scalar 的 range 类型被统一为单一的 dtype 参数化家族。带 Int 参数的 range() 行为与之前完全一致。作为其中一部分,range(...).__len__() 始终返回 Int,并在无符号 range 的元素计数超过 Int.MAX 时断言,而不是静默截断或环绕;如需大小提示,请使用 bounds(),其上界在此情况下为 None。
浮点迭代现在无漂移且可逆,因此正向和反向迭代在重复调用之间、在同一宽度的任何 IEEE-754 平台上产生相同序列。此前像 0.1 这样无法精确表示的步长可能漂移并多产出一个正向元素,随后被 reversed() 丢弃。
reversed() 现在可作用于类型化 range,例如 reversed(range(Int16(1), 10, 2)),因为 ReversibleRange trait 增加了一个关联 ReversedType 迭代器,而不是硬编码其 __reversed__() 返回类型。
非数值元素类型(Bool 和窄 MX 浮点格式)现在在构造时就被拒绝,一参数和两参数的浮点 range(range(Float64(4.5)) 和 range(Float64(0.5), Float64(3.0)))现在是编译错误而不是无限循环;请使用三参数的步进形式。
任何整型标量现在都可以从 Intable 值构造,而不仅仅是 Int,因此将指针地址作为无符号整数获取可直接进行:UInt(p) 而非 UInt(Int(p))。
标量 SIMD 值(length == 1)的 repr() 在 dtype 拥有别名时现在会使用其类型别名打印,因此 repr(UInt32(4)) 是 UInt32(4) 而非 SIMD[DType.uint32, 1](4)。length > 1 的值以及没有别名的标量 dtype(例如 DType.bool)保持 SIMD[...] 形式。String(...) 和 print(...) 的输出不变。
Python 互操作 {#1-0-0-python}
Python 绑定 API 现在使用安全指针:PythonModuleBuilder.def_py_c_function() 为 METH_FASTCALL 回调使用的 PyCFunctionFast 调用约定将其参数数组声明为 Pointer[PyObjectPtr, MutUntrackedOrigin],通过 PythonTypeBuilder.def_method() 注册的类型化 self 方法将其 self 参数声明为 Pointer[Self](例如 self_ptr: Pointer[mut=True, Self]),扩展参数辅助函数 check_and_get_arg() 和 check_and_get_or_convert_arg() 返回安全的 Pointer。这些指针类型共享相同布局,因此 C ABI 和行为不变;请更新签名中的写法,并使用 args[unsafe_offset=i] 读取借用的参数。
PythonObject 的算术、比较和成员运算符现在通过 CPython 的抽象数字、对象和序列协议(例如 PyNumber_Add、PyObject_RichCompare 和 PySequence_Contains)进行分发,而不是先做 Python 级别的属性查找再做绑定方法调用。结合非变更运算符现在借用其操作数而非按值取走,这使互操作热路径(紧凑的 a + b 或 a < b 循环)大约快 12 倍。它也更贴合标准 Python 运算符语义,包括反射操作数回退(__radd__、__rmul__ 等)以及不支持操作的标准错误消息。没有操作数支持的操作现在会引发 TypeError,而此前可能将 NotImplemented 对象作为值返回;用 == 比较不匹配类型现在返回 False 而非真值 NotImplemented。
为新的 std.python.numpy 模块新增了 copy_to_numpy_array() 和 from_numpy_array(),用于在 Mojo Span/List 和 NumPy 数组之间移动扁平数值数据,无需手写 ctypes 管道。两者都支持定宽数值 dtype。copy_to_numpy_array() 将其输入复制到一个新的、独立的数组;from_numpy_array() 零拷贝地借用数组缓冲区。
为 std.python.bindings 新增了 raise_python_exception(),它通过 PyErr_SetString 将 Mojo Error 转换为 Python 异常,并返回一个空的 PyObjectPtr。
系统、FFI 与运行时 {#1-0-0-system-ffi}
OwnedDLHandle 对符号查找和调用有几处变更:
get_function() 现在返回一个在运行期间保持所属句柄存活的调用对象,修复了在符号查找与调用之间库可能被 dlclose 的崩溃。其参数现在是返回类型而非完整的函数指针类型,并在符号缺失时抛异常(此前是中止进程):
# Before:
var sqrt = lib.get_function[def(Float64) abi("C") -> Float64]("sqrt")
# After:
var sqrt = lib.get_function[Float64]("sqrt")
get_function() 和 call() 现在使用 C ABI 而非 Mojo 调用约定转发参数,因此结构体可以按值传递和返回。多字段结构体参数不再在编译时被拒绝。
get_symbol() 现在返回一个借用句柄的指针,而非具有 untracked 源的指针,因此库在已解析符号仍存活时不能再被 dlclose,也不再需要 _ = lib 保活。指针的可变性跟随句柄的可变性。
get_symbol() 的 cstr_name 重载现在接受 CStringSlice 而非 Pointer[mut=False, Int8],因此它要求的空终止由类型表明而非假定。在调用它时,请在 as_c_string_slice() 之后去掉 unsafe_ptr()。
external_call() 现在可以调用 C 可变参数函数。新增的仅关键字参数 num_fixed_args 指定被调用方前导参数中有多少个是固定参数;其余作为可变参数传递:
# int open(const char *path, int oflag, ...);
var fd = external_call["open", c_int, num_fixed_args=2](
path_str.as_c_string_slice().unsafe_ptr(), c_int(flags), c_int(0o666)
)
保持 None 默认值时,被调用方被声明为非可变参数,这会在 ABI 将可变参数与固定参数以不同方式传递的目标上误编译可变参数调用。计数为 0 与 None 不同:它声明了一个所有参数都是可变参数的被调用方。
通过 open() 以 "w"、"rw" 或 "a" 模式打开的文件不再将其权限重写为 0o666。新创建的文件现在是 0o666 & ~umask(在常见的 022 umask 下为 0o644,与 Python 一致),已存在的文件保留其已有权限。
新增 runtime.initialize_runtime(),用于在 Mojo 代码以共享库形式构建(mojo build --emit shared-lib)并被非 Mojo 宿主程序(例如 C 或 C++)调用时初始化 Mojo 运行时。在这种情况下没有 Mojo main() 运行,因此运行时从未被初始化,parallelize() 等并行或异步 API 会崩溃。在使用任何依赖运行时的 API 之前调用 initialize_runtime();该调用是幂等的,并覆盖进程中的所有线程。详情请参见从 C 或 C++ 调用 Mojo 共享库。
chdir() 已添加到 std.os 模块,并为 io.FileDescriptor 新增了 fchdir() 方法。它们是对应 POSIX 函数的封装。
其他库变更 {#1-0-0-other-library}
Bencher.iter() 现在接受一个会抛异常的闭包作为运行时参数,因此函数体会抛异常的基准测试可以传入一个带显式捕获列表的闭包,而不是 @__parameter 闭包。请优先使用统一的闭包形式而非已弃用的 @__parameter 形式。
def bench_add(mut b: Bencher) raises:
@always_inline
def call_fn() raises {var a, var c}:
keep(a + c)
b.iter(call_fn)
当未处理的错误从 main 传播出去且未收集到栈跟踪时,Mojo 现在会打印一条提示,建议设置 MODULAR_DEBUG=stack-trace-on-error 来启用栈跟踪收集,而不是仅打印错误消息。
包迁移 {#1-0-0-gpu-package-moves}
大多数与加速器编程相关的标准库 API 已迁移到新的 max Mojo 包,包括:
std.benchmark.Bench.bench_multicontext ->
max.benchmark.bench_multicontext
std.benchmark.Bencher.iter_custom(DeviceContext) ->
max.benchmark.bencher_iter_custom
std.gpu.compute -> max.gpu.compute
std.gpu.host -> max.gpu.host
std.gpu.memory -> max.gpu.memory
std.gpu.sync -> max.gpu.sync
layout 包现在与 MAX 一起分发而非 Mojo。
一些与 GPU 编程相关的低层 API 仍保留在标准库中。有关最新的 GPU 编程更新,请参见 MAX 26.5 变更日志。
工具链变更 {#1-0-0-tooling-changes}
mojo-lsp-server 默认不再解析或类型检查文档字符串中的代码块。此检查建立在 LSP 服务器中不稳定的基础之上,容易失败,产生与正在编辑的代码无关的误报诊断,而收益甚微。从命令行启动 mojo-lsp-server 时传入 -check-docstrings 可重新启用此前的行为。我们计划使此检查更稳健,并随时间推移重新默认启用。
崩溃报告现在默认跟随 telemetry.enabled 设置,因此两者会一起启用或禁用,除非被覆盖。显式设置 crash_reporting.enabled(或 MODULAR_CRASH_REPORTING_ENABLED 环境变量)仍优先。此前崩溃报告在一条初始化路径中默认禁用,而在另一条生产构建路径中默认启用。
program.crash_reporting_enabled_invocation 遥测事件已被重命名为 program.initialized。每当启用遥测时,它每个进程发出一次,并携带一个 crash_reporting.enabled 属性,记录该会话是否启用了崩溃报告。
新增 --fp-mode CLI 标志,以逗号分隔的列表形式控制浮点行为。目前唯一支持的功能是 contract,取值为 fast(默认)或 off。contract=fast 类似于 Clang 的 -ffp-contract=fast:a + b*c 可以跨语句融合为一次融合乘加,破坏严格 IEEE 兼容性;contract=off 禁用收缩以获得更严格的浮点语义。
新增 --lld-path CLI 标志,覆盖 Mojo 使用的 LLD 路径。