8.輕松入門Sui Move: 對象(下)

上一章我們簡單概括了四種不同類型的對象,這一章將詳細(xì)介紹每種對象的使用方法。期間可能會有些關(guān)于ability的內(nèi)容,如果對ability不太熟悉的朋友,建議先看:9.輕松入門Sui Move: Ability

獨(dú)有對象

獨(dú)有對象屬于某一個賬戶地址或者某個對象ID,只有該賬戶地址(或?qū)ο螅┠茉L問,修改,刪除和轉(zhuǎn)交它。

創(chuàng)建方法

創(chuàng)建一個對象后,使用transfer或者public_transfer把所有權(quán)轉(zhuǎn)交給一個賬戶地址(或?qū)ο驣D),那么這個對象就是獨(dú)有對象。

public entry fun new(ctx: &mut TxContext) {
    let person = Person {
        id: object::new(ctx),
        name: string::utf8(b"hanmeimei"),
    };
    let company = Company {
        id: object::new(ctx),
        person: person,
        can_be_transfered: false,
    };
    //company就是一個獨(dú)有對象
    transfer::transfer(company, tx_context::sender(ctx))
}

那transfer和public_transfer有什么區(qū)別呢?我們來看一下實(shí)現(xiàn)這兩個方法的源碼:

public fun transfer<T: key>(obj: T, recipient: address) {
    transfer_impl(obj, recipient)
}
//public_transfer要求被轉(zhuǎn)交的對象具有key, store ability
public fun public_transfer<T: key + store>(obj: T, recipient: address) {
    transfer_impl(obj, recipient)
}

我們可以看到函數(shù)內(nèi)的實(shí)現(xiàn)完全一樣,只是不同函數(shù)對類型T的限定有區(qū)別:public_transfer方法還要求對象必須有store ability。

根據(jù)這個源碼,我們得出以下結(jié)論:

  • 無論是transfer還是pubic_transfer,都只能用于轉(zhuǎn)交對象,沒有key ability的非對象結(jié)構(gòu)體不能使用。
  • 如果對象沒有store ability 就只能使用transfer

還有一個區(qū)別就是transfer方法只能在定義對象的模塊內(nèi)使用,public_transfer則模塊內(nèi)外均可使用。

總結(jié)來說就是:transfer適用于沒有store ability的對象,只能在定義它的模塊內(nèi)轉(zhuǎn)交。 public_transfer則只能用于有store ability的對象,可以在模塊內(nèi)外轉(zhuǎn)交。

<font size=4>筆者做了一個小實(shí)驗(yàn),使用transfer在模塊內(nèi)轉(zhuǎn)交有store ability的對象,會觸發(fā)警告,不影響運(yùn)行。但不建議這么做。</font>

使用場景

只要是在任何時間點(diǎn)都只有一個擁有者的對象,都應(yīng)該使用獨(dú)有對象。相比共享對象,獨(dú)有對象沒有數(shù)據(jù)爭用的問題,將會有更快的速度,更小的成本和更高的吞吐量,所以獨(dú)有對象應(yīng)用盡用。

不可變對象

不可變對象不能被編輯、刪除和轉(zhuǎn)交。它不屬于任何人,所有人都可以訪問它。

創(chuàng)建方法

使用freeze_object或者public_freeze_object就可以freeze對象,把對象變成不可變對象。注意!這個過程是不可逆的。

//創(chuàng)建一個不可變對象
public fun new_contract(text:String, ctx: &mut TxContext) {
    transfer::freeze_object(Contract{
        id: object::new(ctx),
        text: string::utf8(b"hello world"),
    })
}

freeze_object和public_freeze_object的區(qū)別,跟public_transfer和transfer的區(qū)別一樣,這里不再詳解。

訪問方法

因?yàn)椴豢勺儗ο蟛粚儆谌魏稳?,也不允許編輯,所以不可變對象只允許不可變引用。把對象本身作為參數(shù)或者使用可變引用都會引起報錯,

//正確
public fun access_contract(c: &Contract, _: &mut TxContext)
//報錯
public fun access_contract(c: Contract, _: &mut TxContext)
//報錯
public fun access_contract(c: &mut Contract, _: &mut TxContext)
使用場景

只要對象有不可改變,不能轉(zhuǎn)移,不能刪除的特性都應(yīng)該使用不可變對象。不僅是因?yàn)樗奶匦?,而且它也沒有數(shù)據(jù)爭用問題具有較高性能。一個典型的不可變對象就是我們發(fā)布的包。

共享對象

共享對象是使用share_object或者public_share_object函數(shù)的對象,它屬于所有人,所有人都可以訪問、修改、刪除和轉(zhuǎn)交它。這里值得注意的是,所有人都可以訪問共享對象不代表模塊內(nèi)外都能直接訪問對象內(nèi)字段,模塊外對共享對象字段的訪問也只能通過調(diào)用定義對象的模塊提供的函數(shù)實(shí)現(xiàn)。所有人都可以轉(zhuǎn)交共享對象也不意味著模塊外一定能轉(zhuǎn)交共享對象,模塊外要能轉(zhuǎn)交共享對象,要求對象有store abiity 并且使用public_share_object方法。

share_object和public_share_object的區(qū)別請看transfer和public_transfer

創(chuàng)建方法
public fun new_platform(ctx: &mut TxContext): Admin {
    let platform = RentalPlatform {
        id: object::new(ctx),
        deposit_pool: table::new<ID, u64>(ctx),
        balance: balance::zero<SUI>(),
        notices: table::new<ID, RentalNotice>(ctx),
        owner: tx_context::sender(ctx),
    };

    transfer::share_object(platform);
}
訪問方法
//這三種都可以
public fun access_platform(c: &Contract, _: &mut TxContext)
public fun access_platform(c: Contract, _: &mut TxContext)
public fun access_platform(c: &mut Contract, _: &mut TxContext)
使用場景

只有必要的時候才使用共享對象,因?yàn)樗腥硕寄芫庉?,轉(zhuǎn)交,刪除可能會存在數(shù)據(jù)爭用問題,為了解決數(shù)據(jù)爭用會耗費(fèi)更多時間和資源。

被嵌套的對象

被嵌套的對象被一個對象嵌套在內(nèi),成為外層對象的一部分,在鏈上不能獨(dú)立存在,也無法使用對象ID直接訪問。只能通過嵌套他的對象訪問,修改或轉(zhuǎn)移。如果它又被轉(zhuǎn)交給了一個賬戶地址,這個對象就轉(zhuǎn)變成了獨(dú)有對象,該賬戶的用戶就可以通過對象ID直接訪問了。

嵌套對象的Owner是外層對象ID,那是不是Owner是對象ID的都是嵌套對象呢???別忘了獨(dú)有對象的owner也可能是對象ID,這里需要注意區(qū)分。

注意,非對象結(jié)構(gòu)體也能被嵌套,嵌套時也要求結(jié)構(gòu)體有store ability.但是本文討論的是對象,非對象結(jié)構(gòu)體暫不詳談。

嵌套的方式有三種,分別是直接嵌套,通過Option嵌套和通過vector嵌套。我們接下來依次探究每種嵌套方式。

直接嵌套
創(chuàng)建方式

直接把一個對象作為另外一個對象的字段,這種方式就是直接嵌套。

public struct Company  has key,store {
    id: UID,     
    //嵌套Person對象
    person: Person,
    can_be_transfered: bool,
}
public struct Person has key,store {
    id:UID,
    name: String,
}

注意,不能循環(huán)嵌套,也就是說A嵌套B,B不能嵌套A。

訪問方式

被直接嵌套的對象,不能使用對象ID訪問(sui client object命令也不行),也不能在任何函數(shù)調(diào)用中將其作為參數(shù),唯一的訪問方式就是通過訪問外層對象。

public entry fun access_person(company: &Company,_: &mut TxContext) {
    //使用.一層一層訪問
    let _ = company.person.name;
}
解除嵌套

被嵌套的對象,可以取出轉(zhuǎn)交給賬戶地址,轉(zhuǎn)變成一個獨(dú)有對象。這個過程稱之為解除嵌套。方法如下:

//這里的company對象必須按值傳遞才能獲取到person的對象
public entry fun transfer_person(company: Company, ctx: &mut TxContext) {
    let Company{
        id:id,
        person:person,
        can_be_transfered:can_be_transfered,
    } = company;
    let _ = can_be_transfered;
    //需要使用person對象值來轉(zhuǎn)交
    transfer::public_transfer(person, tx_context::sender(ctx));
    //必須刪除外層對象
    object::delete(id);
}

本段代碼執(zhí)行完,輸出的Transaction Effects 模塊,會有一個 Unwrapped Objects,展示的就是解開嵌套的對象。如下:

unwrap_object.png
應(yīng)用場景

被嵌套的對象無法直接訪問只有通過調(diào)用模塊內(nèi)特定的函數(shù)才能訪問,在這個函數(shù)內(nèi)我們可以設(shè)置訪問的條件,以實(shí)現(xiàn)對對象的訪問限制。

被直接嵌套的對象在解除嵌套的時候,必須刪除外層對象;并且實(shí)例化外層對象的時候也必須同時實(shí)例化被嵌套的對象。直接嵌套適用于外層對象必須有嵌套對象的場景。比如公司必須有員工,沒員工就會倒閉。

那有沒有一種更靈活的方式,外層對象可能嵌套對象,可能沒有嵌套對象,可以動態(tài)嵌套;解除嵌套也無需刪除外層對象?通過Option嵌套就能輕松實(shí)現(xiàn)。

通過Option嵌套

Option是標(biāo)準(zhǔn)庫實(shí)現(xiàn)的一個結(jié)構(gòu)體,含有copy,drop和store ability,內(nèi)部包含一個可指定類型的數(shù)組字段。源碼如下:

//在使用Option類型的時候,需要使用<>指定類型。這是泛型相關(guān)知識后續(xù)將會講到
struct Option<Element> has copy, drop, store {
    vec: vector<Element>
}

那如何使用Option來嵌套對象呢?我們通過一個人和筆記本的例子說明:

創(chuàng)建方式

有的人擁有筆記本,有的人則沒有。所以我們可以聲明一個Person對象,通過Option嵌套Notebook對象。我們先實(shí)例化一個沒有筆記本的Person對象:

public struct Person has key {
    id: UID,
    name: String,
    notebook: Option<Notebook>,
}
//被嵌套的對象必須要有store ability
public struct Notebook has key,store {
    id: UID,
    brand: String,
    model: String,
}
public fun new(ctx: &mut TxContext) {
    transfer::transfer(Person{
        id: object::new(ctx),
        name: string::utf8(b"hanmeimei"),
        //可以實(shí)例化一個沒有Notebook對象的Peroson對象
        notebook: option::none<Notebook>(),
        }, tx_context::sender(ctx));
}

后面這位名為hanmeimei的人購買了一個筆記本,我們使用option::fill方法將Notebook對象嵌入Person對象:

//嵌套Notebook對象
public fun fill_notebook(person: &mut Person, ctx: &mut TxContext) {
    //在將Notebook嵌入Person之前,確定Person沒有嵌套Notebook。否則會報錯
    assert!(option::is_none(&person.notebook), EOptionNotEmpty);

    let notebook = Notebook {
        id: object::new(ctx),
        brand: string::utf8(b"HUAWEI"),
        model: string::utf8(b"v10"),
    };
    //嵌套Notebook
    option::fill<Notebook>(&mut person.notebook, notebook);    
}       
訪問方式

訪問被Option嵌套的對象也只能通過外層對象訪問,可以使用option::borrow方法不可變引用notebook對象,也可以使用option::borrow_mut方法可變引用notebook對象:

public entry fun access_notebook(person: &Person, _: &mut TxContext) {
    let notebook_ref = option::borrow<Notebook>(&person.notebook);
    _ = notebook_ref.brand;
}
解除嵌套

如果要轉(zhuǎn)賣筆記本,則使用option::extract取出,并轉(zhuǎn)交給其他人。

//解除嵌套
public entry fun unwrap_notebook(person: &mut Person, ctx: &mut TxContext) {
    //確認(rèn)有嵌套,否則會報錯
    assert!(option::is_some(&person.notebook), EOptionEmpty);
    //解除嵌套并轉(zhuǎn)交給當(dāng)前用戶
    let notebook = option::extract<Notebook>(&mut person.notebook);
    transfer::public_transfer(notebook, someone);
}
應(yīng)用場景

雖然Option類型的唯一字段是數(shù)組類型,但是Option<element>只能最多只能包含一個對象。所以O(shè)ption適合可能有一個嵌套或者沒有嵌套對象的場景。那如果我想嵌套多個同類型對象怎么辦?我們可以使用vector嵌套對象。

通過vector嵌套

很多人可能沒有筆記本,開發(fā)人員則可能有一個或者多個筆記本,我們使用vector來創(chuàng)建Person對象:

創(chuàng)建方式

我們可以聲明一個Person對象,notebooks字段申明為Notebook類型的數(shù)組。并實(shí)例化一個沒有筆記本的Person對象:

public struct Person has key {
    id: UID,
    name: String,
    notebooks: vector<Notebook>,
}
public struct Notebook has key,store {
    id: UID,
    brand: String,
    model: String,
}
public fun new(ctx: &mut TxContext) {
    transfer::transfer(Person{
        id: object::new(ctx),
        name: string::utf8(b"hanmeimei"),
        notebooks: vector::empty<Notebook>(),
        }, tx_context::sender(ctx));
}
訪問方式

跟option類型類似,可以使用borrow方法不可變引用。不過因?yàn)槭菙?shù)組,在訪問的時候需要指定索引,確實(shí)訪問多個筆記本中的哪一個。

public entry fun access_notebook(person: &Person, index: u64, _: &mut TxContext) {
    //也可以使用vector::borrow_mut方法可變引用
    let notebook_ref = vector::borrow<Notebook>(&person.notebooks, index);
    _ = notebook_ref.brand;
}
解除嵌套

使用vector::remove方法解除嵌套

//解除嵌套
public entry fun unwrap_notebook(person: &mut Person, notebook: &Notebook, ctx: &mut TxContext) {
    //確認(rèn)有嵌套,否則會報錯
    let (contains,index) = vector::index_of<Notebook>(&person.notebooks, notebook);
    assert!(contains, EEmpty);
    //解除嵌套并轉(zhuǎn)交給當(dāng)前用戶
    let notebook = vector::remove<Notebook>(&mut person.notebooks, index);
    transfer::public_transfer(notebook, tx_context::sender(ctx));
}
應(yīng)用場景

vector方法的嵌套,適用于外層對象有零個或者多個同類型嵌套對象的場景。

了解更多Sui Move內(nèi)容:

  • telegram: t.me/move_cn
  • QQ群: 79489587
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容