入门 Sui Move 开发:4. Sui Move 中集合、对象、泛型、动态字段

  • greyhao
  • 更新于 2024-11-15 12:02
  • 阅读 324

Sui Move 中集合、对象、泛型、动态字段的使用。

在第一节入门 Sui Move 开发:1. 环境安装 中我们已经成功安装了 Sui Move 开发环境及开发 IDE。

在第二节入门 Sui Move 开发:2. 常用命令、编写并发布 Hello World 合约 中我们也了解了常用的命令并在链上发布了自己的第一个合约 hello world

在第三节 入门 Sui Move 开发:3. Sui Move 语法和设计模式 中我们了解学习 Sui Move 语法和设计模式,并发布了一个代币到链上。

本节内容其实是对上一节语法的补充。

集合

vector 关键字定义存储多个同类型的变量(数组)。

let personList : vector<Person> = vector<Person>[];

let bools : vector<bool> = vector[false, false, true];

// 简单的例子,vector 提供的一些方法
let mut list: vector<u8> = vector[1, 3, 5];
// 添加一个值
vector::push_back(&mut list, 0);
// 删除最后一个值
vector::pop_back(&mut list);
// list 中数据的个数
vector::length(&list);

Table 是一个映射类的集合,可以动态存储键值对。

// 创建 table 类型变量
let mut table_values: Table<u8, u8> = table::new(ctx);

// 添加键值对
table::add(&mut table_values, 0, 10);

// 删除键值对
table::remove(&mut table_values, 0);

// 获取不可变引用 table 对象 key 对应的值
table::borrow(&table_values, 0);

// 获取可变引用 table 对象 key 对应的值
table::borrow_mut(&mut table_values, 0);

// table 中有 key 返回 true
table::contains(&table_values, 0);

// table 中键值对的个数
table::length(&table_values);

对象

定义对象

对象是一种特殊的结构体,对象具有key对象且第一个字段是全局唯一ID。

// 定义对象结构体
public struct Article has key {
  id: UID,
  name: String,
  content: String,
}

删除对象

开发中积极删除无用对象,减少 gas 消耗。

使用 object::delete 删除 id

public fun drop_article(a: Article) {
  let Article{id: id, title:_, content: _, word_cnt: _} = a;
  object::delete(id); // 删除对象 id 的唯一方法
}

对象的分类

对象分为:

  • 独有对象
  • 共享对象
  • 不可变对象
  • 被嵌套的对象

独有对象

对象的 owner 字段值是账户的地址或者是对象的 ID。它只能属于 owner,可以切换对象的 owner,只有所有者能访问、修改,转移它。

创建一个对象后,使用 transfer::transfertransfer::public_transfer 把所有权交给一个地址或对象 ID,那么这个对象就是独有对象。

transfer 用于没有 store 能力的对象,只能在定义对象的模块中使用,public_transfer 用于有 store 能力的对象,可以在模块内外使用。

public fun new_share(title: vector<u8>, content: vector<u8>, ctx: &mut TxContext) {
    // 创建 Article 实例
    let article = Article {
        id: object::new(ctx),
        title: utf8(title),
        content: contentStr,
    };
    // 将 article 变为独有对象
    transfer::transfer(article, ctx.sender());
} 

共享对象

对象的 owner 字段值带有 Shared 标记,该对象属于所有人,对所有人开放访问,修改的权限。

创建一个对象后,使用 transfer::share_objecttransfer::public_share_object 转移对象,那么对象就变成了共享对象。

不可变对象

对象的 owner 字段值是 Immutable,一旦创建不能修改和转交,但是对所有人开放访问权限。

每次发布包就会返回一个不可变对象,所有人都可以访问这个包但包一经发布不可修改。

创建一个不可变对象,使用 freeze_objectpublic_freeze_object 方法。过程不可逆。

被嵌套的对象

对象可以被作为字段类型定义在另一个对象里。字段对象就是被嵌套的对象。

嵌套的方式:

  • 直接嵌套

    public struct Person has key, store {
      id: UID,
      name: String,
    }
    
    public struct Company has key {
      id: UID,
      // 直接嵌套:嵌套 Person 对象
      person: Person,
      can_be_transfered: bool,
    }
    
    // 访问直接嵌套的对象
    public entry fun transfer_person(company: &Company, _: &mut TxContext) {
      // 通过外层对象 company 来访问被嵌套对象 Person
      let _ = company.person.name;
    }
    
    // 解除嵌套
    public entry fun transfer_person_unwrapped(company: Company, ctx: &mut TxContext) {
      let Company{
        id: id,
        person: person,
        can_be_transfered: can_be_transfered,
      } = company;
      let _ = can_be_transfered;
      // 转移 person 对象,person 就变成了独有对象
      transfer::public_transfer(person, tx_context::sender(ctx));
      // 删除外层对象
      object::delete(id);
    }
  • 通过 Option 嵌套,适用于有一个对象嵌套或者没有嵌套的情况

    // 通过 Option 嵌套
    public struct PersonOption has key {
      id: UID,
      name: String,
      notebook: Option<Notebook>,
    }
    
    public struct Notebook has key, store {
      id: UID,
      brand: String,
      model: String,
    }
    
    public fun newPersonOption(ctx: &mut TxContext) {
      transfer::transfer(PersonOption {
        id: object::new(ctx),
        name: utf8(b"hh"),
        notebook: option::none<Notebook>(), // 实例化没有 Notebook 的 Person 对象
      }, tx_context::sender(ctx));
    }
    
    // 嵌套 Notebook 对象
    public fun fill_notebook(person: &mut PersonOption, ctx: &mut TxContext) {
      // 首先判断之前没有嵌套 Notebook 对象
      assert!(option::is_none(&person.notebook), 1);
    
      let notebook = Notebook {
        id: object::new(ctx),
        brand: utf8(b"b_brand"),
        model: utf8(b"m_model"),
      };
      // 嵌套 notebook
      option::fill<Notebook>(&mut person.notebook, notebook);
    }
    
    // 访问被 option 嵌套的对象
    public entry fun access_notebook(person: &PersonOption, _: &mut TxContext) {
      let notebook_ref = option::borrow<Notebook>(&person.notebook);
      _ = notebook_ref.brand;
    }
    
    // 解除被 option 嵌套的对象
    public entry fun unwrap_notebook(person: &mut PersonOption, ctx: &mut TxContext) {
      // 确认有嵌套
      assert!(option::is_some(&person.notebook), 2);
      // 解除嵌套并转给当前用户
      let notebook = option::extract<Notebook>(&mut person.notebook);
      transfer::public_transfer(notebook, tx_context::sender(ctx));
    }
  • 通过 vector 嵌套,适用于有零个或者多个对象嵌套的情况

    // 通过 vector 嵌套
    public struct PersonVector has key {
      id: UID,
      name: String,
      notebooks: vector<Notebook>,
    }
    
    public struct Notebook has key, store {
      id: UID,
      brand: String,
      model: String,
    }
    
    public fun newPersonVector(ctx: &mut TxContext) {
      transfer::transfer(PersonVector{
        id: object::new(ctx),
        name: utf8(b"person_vector"),
        notebooks: vector::empty<Notebook>(),  // 实例化没有 notebook 的 PersonVector
      }, tx_context::sender(ctx));
    }
    
    // 访问被 vecotr 嵌套的对象
    public entry fun access_notebook_vector(person: &PersonVector, index: u64, _: &mut TxContext) {
      let notebook_ref = vector::borrow(&person.notebooks, index);
      _ = notebook_ref.brand;
    }
    
    // 解除被 vecotr 嵌套的对象
    public entry fun unwrap_notebook_vector(person: &mut PersonVector, notebook: &Notebook, ctx: &mut TxContext) {
      // 确认是否有嵌套
      let (contains, index) = vector::index_of<Notebook>(&person.notebooks, notebook);
      assert!(contains, 2);
      // 解除嵌套,转给用户
      let notebook = vector::remove<Notebook>(&mut person.notebooks, index);
      transfer::public_transfer(notebook, tx_context::sender(ctx));
    }

泛型

通过泛型实现只写一套逻辑,而应用于任何类型上。

定义泛型结构体

定义一个可以装任意对象的箱子。

// 定义字段类型为泛型的结构体,T 就是泛型
public struct Box<T> {
  value: T,
}

限制泛型的 ability

可以使用 : store 来强制传递的泛型有特定 ability

// 强制传递的泛型有 store ability
public struct Box<T: store> has key, store {
  id: UID,
  value: T,
}

函数中使用泛型

函数的参数也可以使用泛型,如下是 transfer::transfer() 的代码。传入的

public fun transfer<T: key>(obj: T, recipient: address) {
    transfer_impl(obj, recipient)
}

动态字段

当对象需要添加非固定类型的字段时,就用到了动态字段。

下面代码中详细介绍了如何创建、删除动态对象。

  // 需要使用模块 dynamic_object_field
  use sui::dynamic_object_field as ofield;

   // 定义添加动态字段的结构体 PersonDynamic
  public struct PersonDynamic has key {
    id: UID,
    name: String,
  }

   // 定义作为动态字段类型的结构体 Phone
  public struct Phone has key, store {
    id: UID,
    pmodel: String,
  }

     // 定义作为动态字段类型的结构体 Computer
  public struct Computer has key, store {
    id: UID,
    cname: String,
  }

  // 给对象添加动态字段
  public entry fun add_obj(person: &mut PersonDynamic, ctx: &mut TxContext) {
    // 创建 Phone 实例
    let phone = Phone { id: object::new(ctx), pmodel: utf8(b"p_1") };
    // person 中添加动态对象字段 Phone
    ofield::add<String, Phone>(&mut person.id, phone.pmodel, phone);
    // person 中添加动态对象字段 Computer
    let computer = Computer { id: object::new(ctx), cname: utf8(b"c_1") };
    ofield::add<String, Computer>(&mut person.id, computer.cname, computer);
  }

  // 删除对象的动态字段
  public entry fun delete_obj(person: &mut PersonDynamic, buyer: address, _: &mut TxContext) {
    // 字段不存在,就停止运行
    assert!(ofield::exists_(&person.id, utf8(b"p_1")), 2);
    let phone: Phone = ofield::remove(&mut person.id, utf8(b"p_1"));
    // 转给其他人
    transfer::public_transfer(phone, buyer);
  }

  // 删除有动态字段的对象
  public entry fun delete_dynamic_obj(mut person: PersonDynamic, _: &mut TxContext) {

    // 字段不存在,就停止运行
    assert!(ofield::exists_(&person.id, utf8(b"c_1")), 0);
    assert!(ofield::exists_(&person.id, utf8(b"b_1")), 1);

    // 删除 phone 动态字段
    let Phone{id: phoneId, pmodel: _} = ofield::remove(&mut person.id, utf8(b"c_1"));
    object::delete(phoneId);

    // 删除 computer
    let Computer{id: computerId, cname: _} = ofield::remove(&mut person.id, utf8(b"c_1"));
    object::delete(computerId);

    // 删除 person 对象
    let PersonDynamic{id: id, name: _} = person;
    object::delete(id);
  }

本节内容结束,本节中仍有很多需要记忆的地方,多写就会变得熟练。

如果觉得本节内容对你有所帮助,可以点赞鼓励下。

如果你对文章中内容有任何疑问可以留言。

点赞 0
收藏 0
分享
本文参与登链社区写作激励计划 ,好文好收益,欢迎正在阅读的你也加入。

0 条评论

请先 登录 后评论
greyhao
greyhao
开发过多链钱包。目前学习 Sui 合约开发中。欢迎一起学习交流