Mojo值的生命周期(Life of a value)详解

到目前为止,我们已经解释了 Mojo 如何允许您使用 Mojo 的所有权模型构建内存安全的高性能代码而无需手动管理内存。但是,Mojo 是为 系统编程而设计的,这通常需要对自定义数据类型进行手动内存管理。因此,Mojo 允许您根据需要执行此操作。需要明确的是,Mojo 没有引用计数器和垃圾收集器。
Mojo 也没有具有特殊权限的内置数据类型。标准库中的所有数据类型(例如Bool、 Int和 String)都以 结构体形式实现。您实际上可以使用MLIR 方言提供的低级原语编写自己的这些类型的替代品 。

Mojo 语言的优点在于它为您提供了这些用于系统编程的低级工具,但在一个框架内,可以帮助您构建安全且易于从高级程序使用的东西。也就是说,您可以深入底层并编写您想要的所有“不安全”代码,但只要您按照 Mojo 的值语义进行操作,实例化您的类型/对象的程序员根本不需要考虑内存管理,而且由于值所有权,行为将是安全且可预测的。

总之,类型作者有责任管理每个值​​类型的内存和资源,方法是根据需要实现特定的生命周期方法,例如构造函数、复制构造函数、移动构造函数和析构函数。Mojo 默认不创建任何构造函数,但它确实为未定义自己的类型添加了一个简单的无操作析构函数。

接下来,我们将准确解释如何根据值语义定义这些生命周期方法,以便您的类型能够很好地与值所有权兼容。

生命周期和寿命(Lifecycles and lifetimes)


首先,让我们澄清一些术语:

  • 值的“生命周期”由结构中的各种dunder方法定义。每个生命周期事件都由不同的方法处理,例如构造函数(__init__())、析构函数(__del__()。所有使用相同类型声明的值都具有相同的生命周期。
  • 值的“生命周期”由程序执行期间每个值被视为有效的时间跨度定义。值的生命周期从初始化时开始,到销毁时结束,通常(但并非总是)从__init__()到__del__()。没有两个值具有完全相同的生命周期,因为每个值都是在不同的时间点创建和销毁的(即使差异是不可感知的)。

Mojo 中值的生命周期始于变量初始化时,一直持续到最后一次使用该值,此时 Mojo 会将其销毁。Mojo 会在不再使用每个值/对象时立即销毁它,使用在每个子表达式之后运行的“尽快”(ASAP) 销毁策略。

您可能可以想象,如果在程序的生命周期中,某个值在多个函数之间多次共享,那么跟踪该值的生命周期可能会很困难。但是,Mojo 通过其值语义和值所有权部分地使这一点变得可预测。生命周期管理的最后一块拼图是值的生命周期:每个值(在结构中定义)都需要实现关键的生命周期方法,这些方法定义了如何创建和销毁值。

值的生命周期(Life of a value)


Mojo 中值的生命周期始于变量初始化时,一直持续到最后一次使用该值,此时 Mojo 会将其销毁。

Mojo 中的所有数据类型(包括标准库中的基本类型,例如 Bool、 Int和 String,以及复杂类型,例如SIMD和 object)都定义为 struct。这意味着任何数据的创建和销毁都遵循相同的生命周期规则,并且您可以定义自己的数据类型,其工作方式完全相同。

Mojo 结构没有任何默认的生命周期方法,例如构造函数、复制构造函数或移动构造函数。这意味着您可以在没有构造函数的情况下创建结构,但您无法实例化它,并且它只能用作静态方法的一种命名空间。例如:

struct NoInstances:
    var state: Int

    @staticmethod
    fn print_hello():
        print("Hello world!")

如果没有构造函数,则无法实例化 this,因此它没有生命周期。该 state字段也是无用的,因为它无法初始化(Mojo 结构不支持默认字段值 - 只有必须在构造函数中初始化它们)。

NoInstances.print_hello()

输出:

Hello world!

以下代码无法通过编译。

   print(NoInstances.state) #cannot access instance field 'state' without an instance of 'NoInstances'

构造函数


要创建 Mojo 类型的实例,需要__init__()构造函数方法。构造函数的主要职责是初始化所有字段。例如:

struct MyPet:
    var name: String
    var age: Int

    fn __init__(inout self, name: String, age: Int):
        self.name = name
        self.age = age

现在我们可以创建一个实例:

var mine = MyPet("Loki", 4)

的实例MyPet也可以被 borrowed 和 destroyed,但目前不能被复制或移动。

我们认为这是一个很好的默认起点,因为没有内置的生命周期事件,也没有意外行为。您(类型作者)必须通过实现复制和移动构造函数来明确决定是否可以复制或移动类型以及如何复制或移动类型。

Mojo 不需要析构函数来销毁对象。只要结构中的所有字段都是可销毁的(标准库中的每个类型都是可销毁的, 指针除外),那么 Mojo 就知道如何在其生命周期结束时销毁该类型。我们将在值的消亡中进一步讨论这一点。

重载构造函数


与其他函数/方法一样,您可以 重载构造函数 __init__()以使用不同的参数初始化对象。例如,您可能需要一个默认构造函数来设置一些默认值并且不接受任何参数,然后需要接受更多参数的附加构造函数。

请注意,为了修改任何字段,每个构造函数都必须使用inout约定声明self参数。如果要从另一个构造函数调用一个构造函数,只需像在外部调用一样调用该构造函数(无需传递self)

例如,下面是如何从重载构造函数委托工作的方法:

struct MyPet:
    var name: String
    var age: Int

    fn __init__(inout self):
        self.name = ""
        self.age = 0

    fn __init__(inout self, name: String):
        self = MyPet()
        self.name = name

字段初始化


请注意,在上例中,每个构造函数结束时,所有字段都必须初始化。这是构造函数中唯一的要求。

事实上,只要所有字段都已初始化,__init__()构造函数就足够聪明,甚至在构造函数完成之前就将self对象视为已完全初始化。例如,一旦所有字段都初始化,此构造函数就可以传递self:

fn use(arg: MyPet):
    pass

struct MyPet:
    var name: String
    var age: Int

    fn __init__(inout self, name: String, age: Int, cond: Bool):
        self.name = name
        if cond:
            self.age = age
            use(self)  # Safe to use immediately!

        self.age = age
        use(self)  # Safe to use immediately!

下面的初始化会报错

struct MyPet:
    var name: String
    var age: Int

    fn __init__(inout self, name
  • 27
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

启航学途

您的鼓励将是我创作的最大动力!

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值