Sui Move 引用详解

在上一节 关于所有权和作用域的讨论中,我们解释了当值传递给函数时,它会移动到函数的作用域。这意味着函数成为该值的拥有者,原始作用域 (原始拥有者) 不再能使用它。这是 Move 中一个重要的概念,它确保值不会同时在多个地方使用。然而,在某些情况下,我们希望将值传递给函数,但仍保留对该值的拥有权。 这正是 引用发挥作用的地方。

为了说明这一点,让我们来看一个简单的例子 - 地铁票应用。我们将介绍 4 种不同的场景:

  1. 乘客可以在售票亭以固定价格购买地铁票。
  2. 可以向检票员出示地铁票以证明乘客拥有有效票卡。
  3. 可以在地铁闸口使用地铁票进入地铁,并扣除一次乘车次数。
  4. 地铁票用完后可以回收。

程序结构

地铁票应用的初始结构很简单。我们定义了 Card 类型和代表单张地铁票乘坐次数的常量 USES。我们还添加了一个错误常量,用于处理地铁票用完的情况。

module book::metro_pass {
    /// Error code for when the card is empty.
    const ENoUses: u64 = 0;

    /// Number of uses for a metro pass card.
    const USES: u8 = 3;

    /// A metro pass card
    public struct Card { uses: u8 }

    /// Purchase a metro pass card.
    public fun purchase(/* pass a Coin */): Card {
        Card { uses: USES }
    }
}

引用

引用是一种向函数展示值而不放弃拥有权的方式。 在我们的例子中,当我们将地铁票出示给检票员时,我们不想放弃对它的拥有权,也不允许他们扣除乘车次数。我们只想允许检票员读取地铁票信息并验证其有效性。

为了做到这一点,在函数签名中,我们使用符号 & 表示我们传递的是值的引用,而不是值本身。

    /// Show the metro pass card to the inspector.
    public fun is_valid(card: &Card): bool {
        card.uses > 0
    }

现在,函数无法获得地铁票的所有权,也不能扣除乘车次数。但是它可以读取地铁票信息。值得注意的是,这样的函数签名使得不带地铁票调用该函数变得不可能。这是一个重要的特性,它允许我们在下一章节讨论的 能力模式

可变引用

在某些情况下,我们希望允许函数更改地铁票的值。例如,当我们在闸口使用地铁票时,我们想要扣除一次乘车次数。为了实现这一点,我们在函数签名中使用关键字 &mut

    /// Use the metro pass card at the turnstile to enter the metro.
    public fun enter_metro(card: &mut Card) {
        assert!(card.uses > 0, ENoUses);
        card.uses = card.uses - 1;
    }

正如您在函数体中看到的,&mut 引用允许修改值,函数可以扣除乘车次数。

按值传递

最后,让我们来看一下将值本身传递给函数会发生什么。在这种情况下,函数获取该值的拥有权,并且原始作用域将无法再使用它。地铁票的所有者可以回收它,因此失去拥有权。

    /// Recycle the metro pass card.
    public fun recycle(card: Card) {
        assert!(card.uses == 0, ENoUses);
        let Card { uses: _ } = card;
    }

recycle 函数中,地铁票被 按值获取,可以解包并销毁。原始作用域无法再使用它。

完整示例

为了展示应用程序的完整流程,让我们将所有部分组合成一个测试。

    #[test]
    fun test_card_2024() {
        // declaring variable as mutable because we modify it
        let mut card = purchase();

        card.enter_metro(); // modify the card but don't move it
        assert!(card.is_valid(), 0); // read the card!

        card.enter_metro(); // modify the card but don't move it
        card.enter_metro(); // modify the card but don't move it

        card.recycle(); // move the card out of the scope
    }