Skip to main content

不可变对象

在 Sui 中,对象可以具有不同类型的所有权,分为两大类:不可变对象和可变对象。不可变对象是无法被改变、转移或删除的对象。不可变对象没有所有者,因此任何人都可以使用它们。

创建不可变对象

要将对象转换为不可变对象,请调用传输模块中的 public_freeze_object 函数:

public native fun public_freeze_object<T: key>(obj: T);

此调用使指定的对象变为不可变。这是一种不可逆转的操作。只有在确信不需要对对象进行修改时,才应该将其冻结。

你可以在 color_object 示例模块 的测试中看到此函数的使用。该测试创建一个新的(拥有的)ColorObject,然后调用 public_freeze_object 将其转换为不可变对象。

{
ts::next_tx(&mut ts, alice);
// Create a new ColorObject
let c = new(255, 0, 255, ts::ctx(&mut ts));
// Make it immutable.
transfer::public_freeze_object(c);
};

在前面的测试中,你必须已经拥有一个 ColorObject 才能传递它。在此调用结束时,此对象将被冻结,并且永远无法被修改。它也不再属于任何人。

transfer::public_freeze_object 函数要求你通过值传递对象。如果允许通过可变引用传递对象,则仍然可以在 public_freeze_object 调用后修改对象。这与其应该已变为不可变的事实相矛盾。

或者,你还可以提供一个在创建时创建不可变对象的 API:

public fun create_immutable(red: u8, green: u8, blue: u8, ctx: &mut TxContext) {
let color_object = new(red, green, blue, ctx);
transfer::public_freeze_object(color_object)
}

此函数创建一个新的 ColorObject 并在它有所有者之前立即使其不可变。

使用不可变对象

在对象变为不可变后,在 Sui Move 调用中可以使用此对象的规则发生变化: 只能将不可变对象作为对 Sui Move 入口函数的只读、不可变引用(&T)进行传递。

任何人都可以使用不可变对象。

考虑一个将一个对象的值复制到另一个对象的函数:

public fun copy_into(from: &ColorObject, into: &mut ColorObject);

在此函数中,任何人都可以将不可变对象作为第一个参数 from 进行传递,但不能作为第二个参数进行传递。 因为你永远无法修改不可变对象,所以即使多个事务同时使用相同的不可变对象,也不会发生数据竞争。因此,不可变对象的存在不对共识提出任何要求。

测试不可变对象

你可以使用 test_scenario::take_immutable<T> 在单元测试中与不可变对象交互,以从全局存储中获取一个不可变对象包装器,以及使用 test_scenario::return_immutable 将包装器返回到全局存储。

test_scenario::take_immutable<T> 函数是必需的,因为你只能通过只读引用来访问不可变对象。test_scenario 运行时会跟踪对此不可变对象的使用情况。如果编译器在下一次事务开始之前未通过 test_scenario::return_immutable 返回对象,则测试将停止。

要看到其实际工作:

let sender1 = @0x1;
let scenario_val = test_scenario::begin(sender1);
let scenario = &mut scenario_val;
{
let ctx = test_scenario::ctx(scenario);
color_object::create_immutable(255, 0, 255, ctx);
};
test_scenario::next_tx(scenario, sender1);
{
// has_most_recent_for_sender returns false for immutable objects.
assert!(!test_scenario::has_most_recent_for_sender<ColorObject>(scenario), 0);
};

此测试以 sender1 身份提交一个事务,尝试创建一个不可变对象。

has_most_recent_for_sender<ColorObject> 函数不再返回 true,因为对象不再拥有。要获取此对象:

// Any sender can work.
let sender2 = @0x2;
test_scenario::next_tx(scenario, sender2);
{
let object = test_scenario::take_immutable<ColorObject>(scenario);
let (red, green, blue) = color_object::get_color(object);
assert!(red == 255 && green == 0 && blue == 255, 0);
test_scenario::return_immutable(object);
};

为了证明此对象确实没有任何所有者,以 sender2 身份开始下一个事务。请注意,它使用了 take_immutable 并成功。这意味着任何发件人都可以获取不可变对象。要返回对象,请调用 return_immutable 函数。

为了检查此对象是否为不可变对象,添加一个尝试修改 ColorObject 的函数:

public fun update(
object: &mut ColorObject,
red: u8, green: u8, blue: u8,
) {
object.red = red;
object.green = green;
object.blue = blue;
}

正如你所了解的,当 ColorObject 是不可变时,该函数将失败。

在链上进行交互

首先,查看你拥有的对象:

export ADDR=`sui client active-address`

sui client objects $ADDR

使用 Sui Client CLI 将 ColorObject 代码发布到链上:

sui client publish $ROOT/examples/move/color_object --gas-budget <GAS-AMOUNT>

如果设置了 $PACKAGE 环境变量,请将包对象 ID 设置为它。然后创建一个新的 ColorObject

sui client call --gas-budget <GAS-AMOUNT> --package $PACKAGE --module "color_object" --function "create" --args 0 255 0

将新创建的对象 ID 设置为 $OBJECT。要查看当前活动地址中的对象:

sui client objects $ADDR

你应该看到一个具有你在 $OBJECT 中使用的 ID 的对象。要将其变为不可变对象:

sui client call --gas-budget <GAS-AMOUNT> --package $PACKAGE --module "color_object" --function "freeze_object" --args \"$OBJECT\"

再次查看对象列表:

sui client objects $ADDR

$OBJECT 不再列出。它不再被任何人拥有。你可以通过查询对象信息来查看它现在是不可变的:

sui client object $OBJECT

响应包括:

Owner: Immutable

如果尝试对其进行修改:

sui client call --gas-budget <GAS-AMOUNT> --package $PACKAGE --module "color_object" --function "update" --args \"$OBJECT\" 0 0 0

响应表示你不能将不可变对象传递给可变参数。