不可变对象
在 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
响应表示你不能将不可变对象传递给可变参数。