sui-move进阶:生命周期对于许多语言的初学者而言,很可能缺乏生命周期的概念,例如通过C语言入门编程的朋友们。但在例如Move和Rust这样的注重安全的编程语言中,生命周期是一个非常重要的概念,甚至许多时候编译器会要求你注意变量的生命周期。在SuiMove编程中,生命周期用于描述变量和
对于许多语言的初学者而言,很可能缺乏生命周期的概念,例如通过C语言入门编程的朋友们。但在例如Move和Rust这样的注重安全的编程语言中,生命周期是一个非常重要的概念,甚至许多时候编译器会要求你注意变量的生命周期。
在 Sui Move 编程中,生命周期用于描述变量和资源的存在周期及其作用范围。Move 的生命周期模型通过静态类型系统强制执行所有权和引用规则,确保资源的安全性,防止常见的内存管理错误,如悬挂引用或双重释放。
本教程将详细讲解 Sui Move 的生命周期管理,包括生命周期的基本概念、作用范围、引用与所有权的交互,以及常见的生命周期规则。
遮蔽
与Move和Copy
两节引用了sui move reference:https://reference.sui-book.com
生命周期指的是变量或资源从创建到销毁的整个过程。在 Move 中,生命周期的管理依赖于以下原则:
所有权规则:资源类型具有唯一的所有权。
作用范围:变量的生命周期受其作用域限制。
静态验证:生命周期在编译时被静态验证,避免运行时错误。
局部变量的生命周期从声明开始,到其所在的代码块结束为止。例如:
fun main() { let x = 10; // x 的生命周期开始 let y = x + 1; // x 和 y 在作用域内 } // x 和 y 的生命周期结束
一个局部变量的声明周期仅在其声明的代码块内(如果不作其他如复制的操作的话),如果尝试在代码块外使用局部变量,将会报错:
let x = 0; { let y = 1; }; x + y // 错误! // ^ 未绑定的局部变量 'y'
一般而言,Move的基本类型都具有drop能力,所以当变量超出作用范围时,Move 会自动清理该变量,并释放相关资源。
资源类型(如 has key 或 has store 的结构体)具有特殊的生命周期规则:
资源的所有权不可复制:资源类型的生命周期由所有权决定,不能通过赋值操作复制资源。
资源的销毁:资源超出作用范围后,必须显式移动或销毁。(除非它具有drop能力)
还是以Coin为例:
public struct Coin<phantom T> has key, store { id: UID, balance: Balance<T>, }
Coin具有key和store能力,这代表它在链上唯一存在,并具有相应的所有权。在创建和使用完Coin之后,我们必须显式地(如果没有,就是你所调用的函数替你做了这一工作)声明移动或是销毁它:
public entry fun mint( treasury_cap: &mut TreasuryCap<MY_COIN>, amount: u64, recipient: address, ctx: &mut TxContext, ) { debug::print(&string(b"my_coin mint")); let coin = coin::mint(treasury_cap, amount, ctx); // 显式地移动Coin transfer::public_transfer(coin, recipient) } public entry fun burn(treasury_cap: &mut TreasuryCap<MY_COIN>, coin: Coin<MY_COIN>) { debug::print(&string(b"burn")); // 显式地销毁Coin coin::burn(treasury_cap, coin); }
引用的生命周期受其创建位置和作用范围的限制:
引用的生命周期不能超过被引用值的生命周期。
Move 不允许引用的引用,以简化生命周期管理。
示例:合法引用
fun main() { let x = 10; let ref_x = &x; // ref_x 的生命周期与 x 相同 let value = *ref_x; // 解引用 ref_x } // x 和 ref_x 生命周期结束
示例:非法引用
fun illegal() { let x = 10; let ref_x = &x; let ref_ref_x = &ref_x; // 错误!Move 不支持引用的引用 }
在 Move 中,资源的所有权可以通过以下方式转移:
显式移动:通过函数调用将资源的所有权从一个变量转移到另一个变量。
资源返回值:将资源作为函数的返回值,转移所有权。
一般而言,我们调用transfer.move中的函数来进行所有权的转移。
所有权转移源代码:
/// Transfer ownership of `obj` to `recipient`. `obj` must have the `key` attribute, /// which (in turn) ensures that `obj` has a globally unique ID. Note that if the recipient /// address represents an object ID, the `obj` sent will be inaccessible after the transfer /// (though they will be retrievable at a future date once new features are added). /// This function has custom rules performed by the Sui Move bytecode verifier that ensures /// that `T` is an object defined in the module where `transfer` is invoked. Use /// `public_transfer` to transfer an object with `store` outside of its module. public fun transfer<T: key>(obj: T, recipient: address) { transfer_impl(obj, recipient) } /// Transfer ownership of `obj` to `recipient`. `obj` must have the `key` attribute, /// which (in turn) ensures that `obj` has a globally unique ID. Note that if the recipient /// address represents an object ID, the `obj` sent will be inaccessible after the transfer /// (though they will be retrievable at a future date once new features are added). /// The object must have `store` to be transferred outside of its module. public fun public_transfer<T: key + store>(obj: T, recipient: address) { transfer_impl(obj, recipient) }
概括一下,就是将obj
的所有权发送给recipient
,而当资源只有key能力时使用tansfer,当资源还具有store能力时使用public_transfer函数。
有趣的是,它们底层都是调用这样一个函数:
public(package) native fun transfer_impl<T: key>(obj: T, recipient: address);
遗憾的是没有找到这一函数的底层实现,如果有了解的,欢迎赐教。
规则 1:变量的生命周期受作用范围限制
变量的生命周期从声明开始,到其作用域结束为止。
规则 2:资源的所有权是唯一的
资源类型具有唯一的所有权,所有权的转移必须显式进行。
规则 3:引用的生命周期不得超出被引用值
引用的生命周期不能超过被引用变量的生命周期,Move 静态验证引用关系。
规则 4:禁止引用的引用
Move 不允许引用的引用,简化了生命周期管理。
如果 let 引入的局部变量与作用域中已有的变量同名,那么之前的变量在此作用域后将无法访问。这称为 遮蔽。
let x = 0; assert!(x == 0, 42); let x = 1; // x 被遮蔽 assert!(x == 1, 42); 当一个局部变量被遮蔽时,它不需要保留之前的类型。 let x = 0; assert!(x == 0, 42); let x = b"hello"; // x 被遮蔽 assert!(x == b"hello", 42);
局部变量被遮蔽后,其值仍然存在,但将不再可访问。这点在处理没有 drop 能力 的类型的值时尤为重要,因为该值的所有权必须在函数结束前转移。

module 0x42::example {
public struct Coin has store { value: u64 }
fun unused_coin(): Coin {
let x = Coin { value: 0 }; // 错误!
// ^ 此局部变量仍包含没有 `drop` 能力的值
x.value = 1;
let x = Coin { value: 10 };
x
// ^ 返回无效
}
}
当局部变量在作用域内被遮蔽时,遮蔽仅在该作用域内有效。一旦作用域结束,遮蔽就消失了。
let x = 0; { let x = 1; assert!(x == 1, 42); }; assert!(x == 0, 42);
请记住,局部变量在被遮蔽时可以改变类型。
let x = 0; { let x = b"hello"; assert!(x = b"hello", 42); }; assert!(x == 0, 42);
在 Move 中,所有局部变量可以通过 move 或 copy 两种方式使用。如果没有明确指定其中一种,Move 编译器可以推断出应该使用 copy 还是 move。这意味着在上述所有例子中,编译器会插入 move 或 copy。
从其他编程语言过来的人会更熟悉 copy,因为它会创建变量内部值的新副本以供表达式使用。使用 copy,局部变量可以多次使用。
let x = 0; let y = copy x + 1; let z = copy x + 2;
任何具有 copy 能力 的值都可以以此方式复制,并且除非指定了 move,否则会自动复制。
move 将值从局部变量中取出,而不复制数据。一旦发生 move,该局部变量将不再可用,即使值的类型具有 copy 能力 也是如此。
let x = 1; let y = move x + 1; // ------ 局部变量在此处被移动 let z = move x + 2; // 错误! // ^^^^^^ 无效使用局部变量 'x' y + z
Move 的类型系统将阻止在值移动后继续使用该值。这与let声明中描述的安全检查相同,防止局部变量在分配值之前被使用。
如上所述,如果未指定 copy 或 move,Move 编译器会推断出应该使用 copy 还是 move。该算法非常简单:
任何具有 copy 能力 的值被视为 copy。
任何引用(可变 &mut 和不可变 &)被视为 copy。
其他任何值被视为 move。
给定以下结构体
public struct Foo has copy, drop, store { f: u64 } public struct Coin has store { value: u64 }
我们有以下示例
let s = b"hello"; let foo = Foo { f: 0 }; let coin = Coin { value: 0 }; let coins = vector[Coin { value: 0 }, Coin { value: 0 }]; let s2 = s; // copy let foo2 = foo; // copy let coin2 = coin; // move let coins2 = coin; // move let x = 0; let b = false; let addr = @0x42; let x_ref = &x; let coin_ref = &mut coin2; let x2 = x; // copy let b2 = b; // copy let addr2 = @0x42; // copy let x_ref2 = x_ref; // copy let coin_ref2 = coin_ref; // copy
Sui Move 的生命周期管理以静态验证为核心,确保资源和引用的安全性:
作用范围限制:变量的生命周期由作用域决定。
资源的唯一所有权:资源的生命周期由所有权规则严格控制。
安全引用:Move 静态验证引用生命周期,防止悬挂引用和数据竞争。
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!