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