bag

模块说明

  • Bag同样是一种类似于map的集合
  • Table同构映射不同,Bag异构映射集合,可以容纳任意类型的键值对
  • Table类似,它的键和值不是存储在 Bag 值中,而是使用Sui的对象系统(sui::dynamic_field)进行存储
  • Bag 结构仅作为一个句柄,用于在对象系统中检索这些键和值
  • 具有完全相同的键值映射的 Bag 值不会被 == 判断为相等
  • Bag必须为空才能被销毁

源码路径

bag.move

方法图解

结构定义

Bag类型没有任何类型参数,可以使用任意类型作为键值。

public struct Bag has key, store {
    /// the ID of this bag
    id: UID,
    /// the number of key-value pairs in the bag
    size: u64,
}

方法说明

分类方法说明
初始化new(ctx: &mut TxContext): Bag创建空Bag
清理destroy_empty<...>(bag: Bag)销毁空Bag,若不为空,将报错:ETableNotEmpty
add<...>(bag: &mut Bag, k: K, v: V)添加键值对到Bag
Key已存在,将报错:EFieldAlreadyExists
remove<...>(bag: &mut Bag, k: K): VBag中删除并返回指定Key的键值对
Key不存在将报错:EFieldDoesNotExist
若值的类型与指定类型不匹配,将报错:EFieldTypeMismatch
borrow_mut<...>(bag: &Bag, k: K): &mut VBag中读取指定Key的值的可变引用,以便进行对值进行修改
Key不存在,将报错:EFieldDoesNotExist
若值的类型与指定类型不匹配,将报错:EFieldTypeMismatch
borrow<...>(bag: &Bag, k: K): &VBag中读取指定Key的值
Key不存在,将报错:EFieldDoesNotExist
若值的类型与指定类型不匹配,将报错:EFieldTypeMismatch
contains<K: ...>(bag: &Bag, k: K): boolBag中包含指定的Key的值返回true,否则返回false
contains_with_type<K: ..., V: store>(bag: &Bag, k: K): boolBag中包含指定的Key的指定类型的值将返回true,否则返回false
length<...>(bag: &Bag): u64 获取Bag的长度
is_empty<...>(bag: &Bag): bool 当且仅当Bag为空时返回true,否则返回false

代码示例

示例中定义了一个书架结构(Bookshelf),其中包含一个Bag类型的items,可以放入任意类型的键值对。示例中以在书架上放置书籍(Book)和玩具(Toy)两种不同的对象为例。

结构定义

public struct Bookshelf has key {
    id: UID,
    items: Bag,
}

// 书籍
public struct Book has key, store {
    id: UID,
    title: String,
    description: String,
}

// 玩具
public struct Toy has key, store {
    id: UID,
    name: String,
    category: String,
}

创建书架共享对象

调用bag::new方法

// 创建书架
public fun create_bookshelf(ctx: &mut TxContext) {
    transfer::share_object(Bookshelf {
        id: object::new(ctx),
        items: bag::new(ctx),
    });
}

放置书本到书架

调用bag::add方法

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

    bookshelf.items.add(book.title, book);
}

放置玩具到书架

调用bag::add方法

public fun add_toy(bookshelf: &mut Bookshelf, name: vector<u8>, category: vector<u8>, ctx: &mut TxContext) {
    let toy = Toy {
        id: object::new(ctx),
        name: ascii::string(name),
        category: ascii::string(category)
    };

    bookshelf.items.add(toy.name, toy);
}

获取书本

调用bag::borrow方法。

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

获取玩具

调用bag::borrow方法。

public fun get_toy(bookshelf: &Bookshelf, name: vector<u8>): &Toy{
    bookshelf.items.borrow(ascii::string(name))
}

设置书本的描述信息

调用bag::borrow_mut方法。

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

判断书本是否存在

调用bag::contains方法。

public fun is_book_existed(bookshelf: &Bookshelf, title: vector<u8>): bool {
    bookshelf.items.contains(ascii::string(title))
}

从书架上移除书本

调用bag::remove方法。

public fun remove_book(bookshelf: &mut Bookshelf, title: vector<u8>): Book {
    bookshelf.items.remove(ascii::string(title))
}

从书架上移除玩具

调用bag::remove方法。

public fun remove_toy(bookshelf: &mut Bookshelf, name: vector<u8>): Toy{
    bookshelf.items.remove(ascii::string(name))
}

判断书架是否为空

调用bag::is_empty方法。

public fun is_bookshelf_empty(bookshelf: &Bookshelf): bool {
    bookshelf.items.is_empty()
}

获取书架上物品数量

调用bag::length方法。

public fun get_count(bookshelf: &Bookshelf): u64{
    bookshelf.items.length()
}

销毁空书架

调用bag::destroy_empty方法。

public fun destroy_empty_bookshelf(bookshelf: Bookshelf) {
    let Bookshelf {id, items} = bookshelf;
    items.destroy_empty();
    id.delete()
}

完整代码

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

    public struct Bookshelf has key {
        id: UID,
        items: Bag,
    }

    // 书籍
    public struct Book has key, store {
        id: UID,
        title: String, 
        description: String,
    }

    // 玩具
    public struct Toy has key, store {
        id: UID,
        name: String, 
        category: String,
    }

    // 创建书架
    public fun create_bookshelf(ctx: &mut TxContext) {
        transfer::share_object(Bookshelf {
            id: object::new(ctx),
            items: bag::new(ctx),
        });
	}

    // 销毁空书架
    public fun destroy_empty_bookshelf(bookshelf: Bookshelf) {
        let Bookshelf {id, items} = bookshelf;
        items.destroy_empty();
        id.delete()
    }

    // 放置书籍
    public fun add_book(bookshelf: &mut Bookshelf, title: vector<u8>, description: vector<u8>, ctx: &mut TxContext) {
        let book = Book {
            id: object::new(ctx),
            title: ascii::string(title),
            description: ascii::string(description)
        };

        bookshelf.items.add(book.title, book);
    }

    // 放置玩具
    public fun add_toy(bookshelf: &mut Bookshelf, name: vector<u8>, category: vector<u8>, ctx: &mut TxContext) {
        let toy = Toy {
            id: object::new(ctx),
            name: ascii::string(name),
            category: ascii::string(category)
        };

        bookshelf.items.add(toy.name, toy);
    }

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

    // 拿取玩具
    public fun get_toy(bookshelf: &Bookshelf, name: vector<u8>): &Toy{
        bookshelf.items.borrow(ascii::string(name))
    }

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

    // 从书架上移除书本
    public fun remove_book(bookshelf: &mut Bookshelf, title: vector<u8>): Book {
        bookshelf.items.remove(ascii::string(title))
    }

    // 从书架上移除玩具
    public fun remove_toy(bookshelf: &mut Bookshelf, name: vector<u8>): Toy{
        bookshelf.items.remove(ascii::string(name))
    }

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

    // 判断书架是否为空
    public fun is_bookshelf_empty(bookshelf: &Bookshelf): bool {
        bookshelf.items.is_empty()
    }

    public fun get_count(bookshelf: &Bookshelf): u64{
        bookshelf.items.length()
    }

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

    public fun get_book_desc(book: &Book): String {
        book.description
    }

    public fun get_toy_name(toy: &Toy): String {
        toy.name
    }

    public fun get_toy_category(toy: &Toy): String {
        toy.category
    }
}
  • bag_tests
#[test_only]
module cookbook::bag_tests {
    use std::ascii;
    use sui::test_scenario as ts;
    use cookbook::bag::{Bookshelf, create_bookshelf, destroy_empty_bookshelf, 
        add_book, add_toy, get_book, get_toy, set_book_desc, is_book_existed, 
        remove_book, is_bookshelf_empty, get_count, get_book_title, get_book_desc,
        remove_toy, get_toy_name, get_toy_category};

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

    #[test]
    public fun test_bag() {
        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,
                ts.ctx(),
            );

            assert_eq(bookshelf.get_count(), 1);
            assert_eq(bookshelf.is_bookshelf_empty(), false);

            ts::return_shared(bookshelf);
        };

        // 放置玩具到书架
        let expected_name= b"Lego set";
        let expected_category= b"Building Blocks";

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

            add_toy(
                &mut bookshelf, 
                expected_name, 
                expected_category,
                ts.ctx(),
            );

            assert_eq(bookshelf.get_count(), 2);

            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));
            };

            {
                let toy = get_toy(
                    &bookshelf, 
                    expected_name, 
                );
                assert_eq(toy.get_toy_name(), ascii::string(expected_name));
                assert_eq(toy.get_toy_category(), ascii::string(expected_category));
            };

            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);

            ts::return_shared(bookshelf);
        };

        // 从书架上拿走书本和玩具
        {
            ts.next_tx(alice);
            let mut bookshelf: Bookshelf = ts.take_shared();

            {
                assert_eq(bookshelf.get_count(), 2);
                let book = remove_book(
                    &mut bookshelf, 
                    expected_title, 
                );
                assert_eq(bookshelf.get_count(), 1);
                assert_eq(book.get_book_title(), ascii::string(expected_title));
                assert_eq(book.get_book_desc(), ascii::string(expected_new_description));
                transfer::public_transfer(book, alice);
            };

            {
                assert_eq(bookshelf.get_count(), 1);
                let toy = remove_toy(
                    &mut bookshelf, 
                    expected_name, 
                );
                assert_eq(bookshelf.get_count(), 0);
                assert_eq(toy.get_toy_name(), ascii::string(expected_name));
                assert_eq(toy.get_toy_category(), ascii::string(expected_category));
                transfer::public_transfer(toy, alice);
            };

            ts::return_shared(bookshelf);
        };

        // 销毁书架
        {
            ts.next_tx(alice);
            let bookshelf: Bookshelf = ts.take_shared();
            destroy_empty_bookshelf(bookshelf);
        };

        ts.end();
    }
}