Struct 是包含複雜數據(或無數據)的自定義類型。它可以描述為一個簡單的 key value 存儲,其中key 是屬性的名稱,value 是存儲的內容。使用關鍵字定義struct
。結構最多可以有 4 個能力,它們是用類型定義指定的。
比較特別是,如果結構值無法複製且無法刪除,我們通常將其稱為*資源。*在這種情況下,資源值必須在函數結束時轉移所有權,默認情況下,結構是線性的和短暫的。意思是它們:不能被複製,不能被刪除,不能被存儲在全局存儲中。這意味著所有值都必須轉移所有權(線性),並且必須在程序執行結束時處理這些值(臨時)。我們可以通過賦予結構能力來放鬆這種行爲(後面會介紹),這些能力允許值被複製或刪除,也可以存儲在全局存儲中或定義全局存儲模式。
未轉移所有權,造成 error
在 Move 中,要創建自定義類型的唯一辦法是使用 Struct
struct NAME {
FIELD1: TYPE1,
FIELD2: TYPE2,
...
}
// 給予 struct 能力
// 擁有四種能力可以使用
// **copy**
// Allows values of types with this ability to be copied.
// **drop**
// Allows values of types with this ability to be popped/dropped.
// **store**
// Allows values of types with this ability to exist inside a struct in global storage.
// **key**
// Allows the type to serve as a key for global storage operations.
struct NAME2 has copy, drop { x: TYPE1, y: TYPE2 }
module M {
// struct can be without fields
// but it is a new type
struct Empty {}
struct MyStruct {
field1: address,
field2: bool,
field3: Empty
}
struct Example {
field1: u8,
field2: address,
field3: u64,
field4: bool,
field5: bool,
// you can use another struct as type
field6: MyStruct
}
}
每一個定義個 struct 都會變成新的類型,可以透過該 Module 進行訪問,就像訪問 Module 裡的 function 一樣。
M::MyStruct;
// or
M::Example;
遞迴結構定義將會錯誤。
struct Foo { x: Foo }
// ^ error! Foo cannot contain Foo
要使用定義的結構,需要創建他的實例,兩者必須在同一個 Module 裡。
module Country {
struct Country {
id: u8,
population: u64
}
// Contry is a return type of this function!
public fun new_country(c_id: u8, c_population: u64): Country {
// structure creation is an expression
let country = Country {
id: c_id,
population: c_population
};
country
}
}
可以透過解構的方式,直接創建實例。
public fun new_country(id: u8, population: u64): Country {
// id matches id: u8 field
// population matches population field
Country {
id,
population
}
// or even in one line: Country { id, population }
}
Move 允許創建空的結構。
public fun empty(): Empty {
Empty {}
}
前面說到,宣告和使用 struct 必須要在同一個 Module, 使用 .(dot) 來進行訪問。\
如果同個 Module 內有兩個 Struct, 我們也可以通過其中一個訪問到另一個 Struct
<struct>.<field>
<struct>.<field>.<nested_struct_field> // and field can be another struct so
// ...
public fun get_country_population(country: Country): u64 {
country.population // <struct>.<property>
}
要拿到 Struct 裡的東西,除了可以直接使用 dot 之外,還可以透過解析 (破壞) 的方式來拿到。
address 0x3 {
module M {
struct Foo has drop { x: u64, y: u64 }
public fun new_foo(): Foo {
Foo { x: 42, y: 10 }
}
}
}
let <STRUCT DEF> = <STRUCT>
example
module Country {
// ...
// we'll return values of this struct outside
public fun destroy(country: Country): (u8, u64) {
// variables must match struct fields
// all struct fields must be specified
let Country { id, population } = country;
// after destruction country is dropped
// but its fields are now variables and
// can be used
(id, population)
}
}
注意,Move 靜止宣告為使用的變量,當解析的 Struct 有不使用的資料時,我們可以這樣寫
module Country {
// ...
public fun destroy(country: Country) {
// this way you destroy struct and don't create unused variables
let Country { id: _, population: _ } = country;
// or take only id and don't init `population` variable
// let Country { id, population: _ } = country;
}
}
前面說到, Struct 都只能在同一個 Module 裡宣告和使用,如果想要外部可讀,我們需要透過 getter 將 Struct 作為返回值進行傳遞。
address 0x3 {
module M {
struct Foo has drop { x: u64, y: u64 } // 給予 Foo Struct drop 能力
public fun new_foo(): Foo {
Foo { x: 42, y: 10 }
}
// getter
public fun x(foo: &Foo): u64 {
foo.x
}
}
}
script {
use 0x1::Debug;
use 0x3::M;
fun main() {
let res = M::new_foo();
Debug::print(&res); // print {42, 10}
Debug::print(&res.x); // 會 error, 必須使用 getter 拿到
Debug::print(&M::x(&res)); // print 42
}
}
本篇詳細介紹 Struct 以及如何使用,開頭題到的四種能力,之後會更詳細說明,只要記得在 Move 默認情況下,Struct 是線性的和短暫的。同時不能被複製,不能被刪除,不能被存儲在全局存儲中。
在 Module 外想要拿到 Struct 則需要透過 getter 來傳遞個別回傳值,我們 Move to Day10