Mojo 的所有权之前主要体现在传参时候的 borrowed
, inout
以及 owned
这三个关键字保证原先传入的值不会被修改,但是从目前 Nightly 版本的发展状况和一系列已有的提案来看,这些关键字、引用的语法和语义可能都会发生比较大的变化。
从目前写 Mojo 的感受来看,很多特性实际上都是半成品的状态,很多时候都得使用 DTypePointer
或者 UnsafePointer
这两种不安全的类型来实现一些功能。这显然不是一个好的设计。所以我希望从目前的提案和 stdlib 中的一些新修改中总结一下 Mojo 目前所有权、生命周期和引用的状况。
关键字的修改
关键字的修改实际上和语言的特性并无直接关系,但是对语义和概念的理解会有很大的帮助。从目前对关键字重命名的提案来看[1],目前的 borrowed
,inout
以及 owned
对于后续生命周期的加入、引用类型的设计都不是很合适,所以这个提案中想使用 ref
, mutref
来代替 borrowed
和 inout
。另外,对于 owned
,提案中想使用 var
来代替。
People on the forums have pointed out that the “owned” keyword in argument lists is very analogous to the var keyword. It defines a new, whole, value and it is mutable just like var. Switching to var eliminates a concept and reduces the number of keywords we are introducing.
这个关键字的修改是比较合理的,毕竟之前 Mojo 的引用、生命周期还不完善,borrowed
,inout
,owned
实际上只是用于传参(Argument Convention)。但是之后引用等特性的加入可能会出现例如 List[ref A]
这样的类型,于是引用就从传参的方式成为了类型系统的一部分,这时候 ref
和 mutref
好像确实看着更舒适一些(而且打得字少)。
引用类型
除了以上关键字替换的提案,最近还有一个新的关于 ref
以及 Reference
类型的提案[2]。这个提案实际上包括了四个部分。首先是将 ref
引入函数调用中,作为一个新的 Argument Convention,并且在使用 ref [<lifetime>]
的方式指定生命周期。另外,在返回值的地方也引入 ref
,并且提供 auto-dereference 这样的特性[3]以方便使用。但是这个提案同时也保留 Reference
类型,作为强制手动解引用的一个选择。
基于以上这两种新特性,这个提案认为可以直接移除 __refitem__
这个之前在 stdlib 中使用的 dunder method, 而直接使用 __getitem__
。下面是提案中给出的新代码:
fn __getitem__(ref [_] self, index: Int) -> ref [__lifetime_of(self)] Self.ElementType:
...
最后这个提案还建议将 Reference
类型重命名成 Pointer
/SafePointer
,因为之前 stdlib 里面的 Pointer
已经被改为了 LegacyPointer
,且目前解引用时使用的 ptr[]
这个看着很像是一个没有实现好的特性。
Because there is no automatic dereference, the existing Reference type currently behaves like a pointer, not a reference. You need to explicitly dereference it with ptr just like an unsafe pointer… but it adds safety through lifetime management and semantic checking.
Let’s just rename it to Pointer: The consequence of this is that explicit dereference syntax is a feature, not a bug. Furthermore, this entire feature area becomes more explainable and separate from the existing Python reference types. This allows a consistent story about how Mojo supports both Pointer (for use when building data structures) and UnsafePointer (for interacting with C code).
这个提案之后还有一些 Syntax Alternative 的内容,包括重命名之前的 borrowed
等关键词,但是似乎目前这些更改还没有最终决定,毕竟 Argument Convention 和生命周期的标记方式等特性还暂时没有确定
Regardless, we should postpone any final decisions until argument conventions and lifetimes have fully settled.
值得注意的是,这个提案中明确指出 ref
并非类型的一部分,而是指调用时对于参数的约定。而在生命周期特性较为完善的 Rust 中,&
,&mut
以及生命周期标注实际上是类型系统的一部分[4],而在 Mojo 目前的提案中,ref
只能够在函数参数或者返回中出现,而 Reference
则是作为类型标记中可以使用的应用类型:
The purpose of
Reference
is to allow references to be stored in data structures. Furthermore, it supports nesting such asReference[Reference[Reference[Int], ...], ...], ...]
without implicit promotions interfering.
这也就使得返回一个 Tuple 的时候,内部的类型不能是 ref
,而只能是 Reference
,所以也就不能直接使用 auto-dereference 的特性了[5]。
总的来说,之后的发展方向应该会包括以下几个方面:
- 生命周期参数,可能之后会在 Parameter 中指定(如
[life: Lifetime]
) ref[_]
关键字用于确定传参方式和返回值返回时的 auto-dereferenceReference
类型(或者命名为Pointer
)作为一个可以用于类型标记的引用类型- 更加明确的 Argument Convention 关键字,可能改变目前的
borrowed
等关键字
一些想法
截止目前(mojo 2024.5.3005 (0c465b6a)
)stdlib 中还没有完全实现参数里面使用 ref [_] self
这样的做法,但是返回一个引用以及 ref [__lifetime_of(self)]
已经被支持了,以下的代码也能够运行并且得到正确的结果:
struct Pair:
var first: Int
var second: Int
fn __init__(inout self, first: Int, second: Int):
self.first = first
self.second = second
fn get_first_ref(inout self) -> ref [__lifetime_of(self)] Int:
return self.first
fn main():
var somePair = Pair(3, 4)
print(somePair.first) # 3
somePair.get_first_ref() = 1
print(somePair.first) # 1
目前 Mojo 对于引用、生命周期的支持还是不完善的(甚至还没有一个明确的对生命周期参数/标记的支持),但是可以期待的是下一个 Stable 版本的 Mojo 编译器应该就会支持诸如 ref
这样的语法特性了(毕竟已经在 Mojo unreleased changelog 中提到了)。另外,尽管目前编译器对生命周期的支持还不完善[6],但诸如 StringSlice 这样的带生命周期保证的类型已经能正确编译了:
fn main():
var s = String("Hello, World!")
var s_ref = s._strref_dangerous()
print(s_ref)
var s_slice = s.as_string_slice()
print(s_slice)
此处 s_ref
这样直接指向字符串内部的类型也能够正确输出,说明 ASAP 应该是被延后到了 s_slice
之后。
此处对生命周期和这一系列提案主要内容的总结和最后 Mojo 的完整解决方案可能还会存在较大的差别,等最终完整的解决方案定稿之后或许可以再和 Rust 和 C++ 进行一些对比。
见提案中 Multiple results wouldn’t get automatic dereference 这一节的内容 ↩︎
关于生命周期的提案见 Provenance Tracking and Lifetimes in Mojo ↩︎