dynamic_field

模块说明

  • dynamic_field(动态字段)模块定义将结构体和值组合在一起
  • 可以使用任意名字做字段,也可以在运行时动态进行添加和删除
  • 名称可以是任何拥有 copydropstore 能力的值,这些值包括基本类型以及拥有相应能力的结构体
  • 任何具有 store 能力的值都可以被存储
  • 可以不具备key能力,即不可直接从外部进行访问

源码路径

dynamic_field.move

方法图解

方法说明

分类方法说明
add<...>(object: &mut UID, name: Name, value: Value)向对象object添加名为name的值为value的动态字段
remove<...>(object: &mut UID, name: Name): Value从对象object中删除名为name的动态字段,若不存在将会报错
remove_if_exists<...>(object: &mut UID, name: Name): Option<Value>从对象object中删除名为name的动态字段,若存在则已option::some包装后返回,若不存在返回option::none()
borrow_mut<...>(object: &mut UID, name: Name): &mut Value从对象object中获取名为name的动态字段的可变引用,以便进行对动态字段的修改
borrow<...>(object: &UID, name: Name): &Value从对象object中获取名为name的动态字段的只读引用,用于进行信息查看
exists_<...>(object: &UID, name: Name): bool若对象object中存在名为name的动态字段则返回true,无需指定value类型
exists_with_type<...>(object: &UID, name: Name): bool若对象object中存在名为name的动态字段则返回true,需指定value类型

代码示例

采用书架和书本的示例,书本对象作为动态字段添加到书架上。

结构定义

// 书架结构定义
public struct Bookshelf has key {
    id: UID,
    // 书本数量
    book_count: u64
}

// 书本结构定义
public struct Book has store {
    // 书本标题
    title: String,
    // 书本描述
    description: String,
}

创建书架共享对象

public fun create_bookshelf(ctx: &mut TxContext) {
    transfer::share_object(Bookshelf {
        id: object::new(ctx),
        book_count: 0,
    });
}

添加书本到书架

调用dynamic_field::add方法。

public fun add_book(bookshelf: &mut Bookshelf, title: vector<u8>, description: vector<u8>) {
    let book = Book {
        title: ascii::string(title),
        description: ascii::string(description)
    };

    dynamic_field::add(&mut bookshelf.id,title, book);
    bookshelf.book_count = bookshelf.book_count + 1;
}

获取书本

调用dynamic_field::borrow方法。

public fun get_book(bookshelf: &Bookshelf, title: vector<u8>): &Book {
    dynamic_field::borrow(&bookshelf.id, title)
}

设置书本的描述信息

调用dynamic_field::borrow_mut方法。

public fun set_book_desc(bookshelf: &mut Bookshelf, title: vector<u8>, description: vector<u8>) {
    let book_mut_ref: &mut Book = dynamic_field::borrow_mut(&mut bookshelf.id, title);
    book_mut_ref.description = ascii::string(description);
}

判断书本是否存在

调用dynamic_field::exists_dynamic_field::exists_with_type方法。

public fun is_book_existed(bookshelf: &Bookshelf, title: vector<u8>): bool {
    dynamic_field::exists_(&bookshelf.id, title)
}

public fun is_book_exists_with_type(bookshelf: &Bookshelf, title: vector<u8>): bool {
    dynamic_field::exists_with_type<vector<u8>, Book>(&bookshelf.id, title)
}

从书架上移除书本

调用dynamic_field::removedynamic_field::remove_if_exists方法。

public fun remove_book(bookshelf: &mut Bookshelf, title: vector<u8>): Book {
    bookshelf.book_count = bookshelf.book_count - 1;
    dynamic_field::remove<vector<u8>, Book>(&mut bookshelf.id, title)
}

public fun remove_if_book_exists(bookshelf: &mut Bookshelf, title: vector<u8>): Option<Book> {
    bookshelf.book_count = bookshelf.book_count - 1;
    dynamic_field::remove_if_exists<vector<u8>, Book>(&mut bookshelf.id, title)
}

完整代码

  • dynamic_field
module cookbook::dynamic_field{
    use std::ascii::{Self, String};
    use sui::dynamic_field;

    public struct Bookshelf has key {
        id: UID,
        book_count: u64
    }

    public struct Book has store {
        title: String, 
        description: String,
    }

    // 创建书架共享对象
    public fun create_bookshelf(ctx: &mut TxContext) {
        transfer::share_object(Bookshelf {
            id: object::new(ctx),
            book_count: 0,
        });
	}

    // 放置书本到书架
    public fun add_book(bookshelf: &mut Bookshelf, title: vector<u8>, description: vector<u8>) {
        let book = Book {
            title: ascii::string(title),
            description: ascii::string(description)
        };

        dynamic_field::add<vector<u8>, Book>(&mut bookshelf.id,title, book); 
        bookshelf.book_count = bookshelf.book_count + 1;
    }

    public fun add_book_obj(bookshelf: &mut Bookshelf, book: Book) {
        dynamic_field::add<vector<u8>, Book>(&mut bookshelf.id,
            book.title.into_bytes(), book); 
        bookshelf.book_count = bookshelf.book_count + 1;
    }

    // 拿取书本
    public fun get_book(bookshelf: &Bookshelf, title: vector<u8>): &Book {
        dynamic_field::borrow(&bookshelf.id, title)
    }

    // 设置书本的描述信息
    public fun set_book_desc(bookshelf: &mut Bookshelf, title: vector<u8>, description: vector<u8>) {
        let book_mut_ref: &mut Book = dynamic_field::borrow_mut(&mut bookshelf.id, title);
        book_mut_ref.description = ascii::string(description);
    }

    // 判断书本是否存在
    public fun is_book_existed(bookshelf: &Bookshelf, title: vector<u8>): bool {
        dynamic_field::exists_(&bookshelf.id, title)
    }

    public fun is_book_exists_with_type(bookshelf: &Bookshelf, title: vector<u8>): bool {
        dynamic_field::exists_with_type<vector<u8>, Book>(&bookshelf.id, title)
    }

    // 从书架上移除书本
    public fun remove_book(bookshelf: &mut Bookshelf, title: vector<u8>): Book {
        bookshelf.book_count = bookshelf.book_count - 1;
        dynamic_field::remove<vector<u8>, Book>(&mut bookshelf.id, title)
    }

    // 如果存在指定标题书本,则从书架上移除书本
    public fun remove_if_book_exists(bookshelf: &mut Bookshelf, title: vector<u8>): Option<Book> {
        bookshelf.book_count = bookshelf.book_count - 1;
        dynamic_field::remove_if_exists<vector<u8>, Book>(&mut bookshelf.id, title)
    }

    public fun get_book_count(bookshelf: &Bookshelf): u64{
        bookshelf.book_count
    }

    public fun get_book_title(book: &Book): String {
        book.title
    }

    public fun get_book_desc(book: &Book): String {
        book.description
    }
}
  • dynamic_field_tests
#[test_only]
module cookbook::dynamic_field_tests {
    use std::ascii;
    use sui::test_scenario as ts;
    use cookbook::dynamic_field::{Bookshelf, create_bookshelf, add_book, get_book, 
        set_book_desc, is_book_existed, is_book_exists_with_type, remove_book,
        get_book_count, get_book_title, get_book_desc};

    #[test_only]
    use sui::test_utils::assert_eq;

    #[test]
    public fun test_dynamic_field() {
        let alice = @0xa;    

        let mut ts = ts::begin(alice);

        // 创建书架
        {
            create_bookshelf(ts.ctx());
        };

        // 放置书本到书架
        let expected_title = b"Mastering Bitcoin";
        let expected_description= b"1st Edition";
        let expected_new_description= b"3rd Edition";

        {
            ts.next_tx(alice);
            let mut bookshelf: Bookshelf = ts.take_shared();

            add_book(
                &mut bookshelf, 
                expected_title, 
                expected_description,
            );

            assert_eq(bookshelf.get_book_count(), 1);

            ts::return_shared(bookshelf);
        };

        // 拿取书本
        {
            ts.next_tx(alice);
            let bookshelf: Bookshelf = ts.take_shared();

            let book = get_book(
                &bookshelf, 
                expected_title, 
            );

            assert_eq(book.get_book_title(), ascii::string(expected_title));
            assert_eq(book.get_book_desc(), ascii::string(expected_description));

            ts::return_shared(bookshelf);
        };

        // 设置书本的描述信息
        {
            ts.next_tx(alice);
            let mut bookshelf: Bookshelf = ts.take_shared();

            set_book_desc(
                &mut bookshelf, 
                expected_title, 
                expected_new_description,
            );

            let book = get_book(
                &bookshelf, 
                expected_title, 
            );

            assert_eq(book.get_book_title(), ascii::string(expected_title));
            assert_eq(book.get_book_desc(), ascii::string(expected_new_description));

            ts::return_shared(bookshelf);
        };

        // 判断书本是否存在
        {
            ts.next_tx(alice);
            let bookshelf: Bookshelf = ts.take_shared();

            let is_existed = is_book_existed(
                &bookshelf, 
                expected_title, 
            );
            assert_eq(is_existed, true);

            let is_existed = is_book_exists_with_type(
                &bookshelf, 
                expected_title, 
            );
            assert_eq(is_existed, true);

            ts::return_shared(bookshelf);
        };

        // 从书架上移除书本
        {
            ts.next_tx(alice);
            let mut bookshelf: Bookshelf = ts.take_shared();

            assert_eq(bookshelf.get_book_count(), 1);

            let book = remove_book(
                &mut bookshelf, 
                expected_title, 
            );

            assert_eq(bookshelf.get_book_count(), 0);
            assert_eq(book.get_book_title(), ascii::string(expected_title));
            assert_eq(book.get_book_desc(), ascii::string(expected_new_description));

            bookshelf.add_book_obj(book);

            ts::return_shared(bookshelf);
        };

        ts.end();
    }
}