引言suimove中的动态字段,可以不在对象发布时而是在运行时进行增添和删除,并且可以储存异构值。本文通过阅读分析dynamic_field和dynamic_object_field中对两种类型的动态字段的实现,理解动态字段的运行方式,以便理解table,bag类型的实现原理。
sui move中的动态字段,可以不在对象发布时而是在运行时进行增添和删除,并且可以储存异构值。本文通过阅读分析 dynamic_field 和 dynamic_object_field中对两种类型的动态字段的实现,理解动态字段的运行方式,以便理解table,bag类型的实现原理。
dynamic_filed实现了动态字段。
定义了一个结构体
/// Internal object used for storing the field and value
struct Field<Name: copy + drop + store, Value: store> has key {
/// Determined by the hash of the object ID, the field name value and it's type,
/// i.e. hash(parent.id || name || Name)
id: UID,
/// The value for the name of this field
name: Name,
/// The value bound to this field
value: Value,
}
可以将Field看成一个包含了键值对的obj,其中,name属性为键,它可以是任何实现了copy drop store特征的类型,value为值,需要具有store特征
/// Adds a dynamic field to the object `object: &mut UID` at field specified by `name: Name`.
/// Aborts with `EFieldAlreadyExists` if the object already has that field with that name.
public fun add<Name: copy + drop + store, Value: store>(
// we use &mut UID in several spots for access control
object: &mut UID,
name: Name,
value: Value,
) {
let object_addr = object::uid_to_address(object);
let hash = hash_type_and_key(object_addr, name);
assert!(!has_child_object(object_addr, hash), EFieldAlreadyExists);
let field = Field {
id: object::new_uid_from_hash(hash),
name,
value,
};
add_child_object(object_addr, field)
}
add用于为一个obj添加动态字段,通过阅读add实现可以看出,为obj添加动态字段其实是为obj添加了一个属于它的子obj,这个子obj就是field。
由于需要通过一个键来索引一个特定的值,所以每个obj下不能重复添加相同的键。
先将obj的address和name进行hash,然后检查hash是否已经存在,如果已经存在,说明obj中已经添加了此键的动态对象,交易将被回滚。
如果name未被添加,创建一个field,将他添加至obj的子对象。
public fun remove<Name: copy + drop + store, Value: store>(
object: &mut UID,
name: Name,
): Value {
let object_addr = object::uid_to_address(object);
let hash = hash_type_and_key(object_addr, name);
let Field { id, name: _, value } = remove_child_object<Field<Name, Value>>(object_addr, hash);
object::delete(id);
value
}
remove用于将一个对象一个键对应的动态字段删除,并取出其中的值,同时filed被解构。
/// Removes the dynamic field if it exists. Returns the `some(Value)` if it exists or none otherwise.
public fun remove_if_exists<Name: copy + drop + store, Value: store>(
object: &mut UID,
name: Name
): Option<Value> {
if (exists_<Name>(object, name)) {
option::some(remove(object, name))
} else {
option::none()
}
}
这个函数与remove功能相同,只是多了一步检查键是否存在,如果存在调用remove移除并返回value,如果不存在返回none
/// Immutably borrows the `object`s dynamic field with the name specified by `name: Name`.
/// Aborts with `EFieldDoesNotExist` if the object does not have a field with that name.
/// Aborts with `EFieldTypeMismatch` if the field exists, but the value does not have the specified
/// type.
public fun borrow<Name: copy + drop + store, Value: store>(
object: &UID,
name: Name,
): &Value {
let object_addr = object::uid_to_address(object);
let hash = hash_type_and_key(object_addr, name);
let field = borrow_child_object<Field<Name, Value>>(object, hash);
&field.value
}
/// Mutably borrows the `object`s dynamic field with the name specified by `name: Name`.
/// Aborts with `EFieldDoesNotExist` if the object does not have a field with that name.
/// Aborts with `EFieldTypeMismatch` if the field exists, but the value does not have the specified
/// type.
public fun borrow_mut<Name: copy + drop + store, Value: store>(
object: &mut UID,
name: Name,
): &mut Value {
let object_addr = object::uid_to_address(object);
let hash = hash_type_and_key(object_addr, name);
let field = borrow_child_object_mut<Field<Name, Value>>(object, hash);
&mut field.value
}
这两个函数分别返回了对应键filed中值的不可变借用和可变借用,我们可以根据需求调用这两个函数取出动态字段中的值
public fun exists_<Name: copy + drop + store>(
object: &UID,
name: Name,
): bool {
let object_addr = object::uid_to_address(object);
let hash = hash_type_and_key(object_addr, name);
has_child_object(object_addr, hash)
}
public fun exists_with_type<Name: copy + drop + store, Value: store>(
object: &UID,
name: Name,
): bool {
let object_addr = object::uid_to_address(object);
let hash = hash_type_and_key(object_addr, name);
has_child_object_with_ty<Field<Name, Value>>(object_addr, hash)
}
这两个函数用来检查obj是否含有相应键的动态对象,这两个函数的区别是,exists_with_type的检查更为严格,他会检查键和值的类型是否符合预期。
public(friend) fun field_info<Name: copy + drop + store>(
object: &UID,
name: Name,
): (&UID, address) {
let object_addr = object::uid_to_address(object);
let hash = hash_type_and_key(object_addr, name);
let Field { id, name: _, value } = borrow_child_object<Field<Name, ID>>(object, hash);
(id, object::id_to_address(value))
}
public(friend) fun field_info_mut<Name: copy + drop + store>(
object: &mut UID,
name: Name,
): (&mut UID, address) {
let object_addr = object::uid_to_address(object);
let hash = hash_type_and_key(object_addr, name);
let Field { id, name: _, value } = borrow_child_object_mut<Field<Name, ID>>(object, hash);
(id, object::id_to_address(value))
}
这两个函数只能被dynamic_object_field调用,是在创建动态对象字段需要使用的
dynamic_object_field在动态字段基础上实现了动态对象字段。
动态对象字段与动态字段的区别是,它的值必须是一个move obj,他被添加后仍能通过其 ID 由外部工具访问,而动态字段则被视为已封装,并且无法通过其 ID 由外部工具(探测器、钱包等)访问存储。
public fun add<Name: copy + drop + store, Value: key + store>(
// we use &mut UID in several spots for access control
object: &mut UID,
name: Name,
value: Value,
) {
let key = Wrapper { name };
let id = object::id(&value);
field::add(object, key, id);
let (field, _) = field::field_info<Wrapper<Name>>(object, key);
add_child_object(object::uid_to_address(field), value);
}
public fun remove<Name: copy + drop + store, Value: key + store>(
object: &mut UID,
name: Name,
): Value {
let key = Wrapper { name };
let (field, value_id) = field::field_info<Wrapper<Name>>(object, key);
let value = remove_child_object<Value>(object::uid_to_address(field), value_id);
field::remove<Wrapper<Name>, ID>(object, key);
value
}
与动态字段的区别是,先将键包装成Wapper,然后调用add函数储存,然后将创建的filed的id取出,再将值添加为filed的子obj,这样,我们就能通过id访问值了。
public fun borrow<Name: copy + drop + store, Value: key + store>(
object: &UID,
name: Name,
): &Value {
let key = Wrapper { name };
let (field, value_id) = field::field_info<Wrapper<Name>>(object, key);
borrow_child_object<Value>(field, value_id)
}
/// Mutably borrows the `object`s dynamic object field with the name specified by `name: Name`.
/// Aborts with `EFieldDoesNotExist` if the object does not have a field with that name.
/// Aborts with `EFieldTypeMismatch` if the field exists, but the value object does not have the
/// specified type.
public fun borrow_mut<Name: copy + drop + store, Value: key + store>(
object: &mut UID,
name: Name,
): &mut Value {
let key = Wrapper { name };
let (field, value_id) = field::field_info_mut<Wrapper<Name>>(object, key);
borrow_child_object_mut<Value>(field, value_id)
}
这两个函数分别返回了键中对应值的不可变借用和可变借用
public fun exists_<Name: copy + drop + store>(
object: &UID,
name: Name,
): bool {
let key = Wrapper { name };
field::exists_with_type<Wrapper<Name>, ID>(object, key)
}
/// Returns true if and only if the `object` has a dynamic field with the name specified by
/// `name: Name` with an assigned value of type `Value`.
public fun exists_with_type<Name: copy + drop + store, Value: key + store>(
object: &UID,
name: Name,
): bool {
let key = Wrapper { name };
if (!field::exists_with_type<Wrapper<Name>, ID>(object, key)) return false;
let (field, value_id) = field::field_info<Wrapper<Name>>(object, key);
field::has_child_object_with_ty<Value>(object::uid_to_address(field), value_id)
}
/// Returns the ID of the object associated with the dynamic object field
/// Returns none otherwise
public fun id<Name: copy + drop + store>(
object: &UID,
name: Name,
): Option<ID> {
let key = Wrapper { name };
if (!field::exists_with_type<Wrapper<Name>, ID>(object, key)) return option::none();
let (_field, value_id) = field::field_info<Wrapper<Name>>(object, key);
option::some(object::id_from_address(value_id))
}
检查动态对象字段是否存在,exists_with_type的检查更为严格,他会检查键和值的类型是否符合预期。
id返回对应键field的id
本文介绍了在 Sui Move 中动态字段的实现,包括 dynamic_field 和 dynamic_object_field 两种类型。这两种类型都允许在对象运行时动态添加和移除字段,并且可以存储异构值。通过阅读和分析动态字段的实现,更好地理解 Sui Move 中动态字段的运行方式,为理解其他类型(如 table、bag)的实现原理提供基础。
<!--StartFragment-->
Move语言学习交流QQ群: 79489587\ Sui官方中文开发者电报群: <https://t.me/sui_dev_cn>
<!--EndFragment-->
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!