Sui move 动态字段

引言

sui move中的动态字段,可以不在对象发布时而是在运行时进行增添和删除,并且可以储存异构值。本文通过阅读分析 dynamic_field 和 dynamic_object_field中对两种类型的动态字段的实现,理解动态字段的运行方式,以便理解table,bag类型的实现原理。

dynamic_filed

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

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)的实现原理提供基础。

Move语言学习交流QQ群: 79489587
Sui官方中文开发者电报群: https://t.me/sui_dev_cn

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值