结构体与资源
结构体 是一种包含类型化字段的用户自定义数据结构.结构体可以存储任何非引用类型,包括其他结构体.
如果结构体值不能被复制且不能被丢弃,我们通常将其称为 资源 . 在这种情况下,资源值必须在函数结束时转移所有权.这一特性使得资源特别适合用于定义全局存储模式或表示重要值(例如代币).
默认情况下,结构体是线性且短暂的. 这意味着它们:不能被复制,不能被丢弃,也不能存储在全局存储中.这表示所有值都必须转移所有权(线性), 并且这些值必须在程序执行结束时被处理(短暂). 我们可以通过为结构体添加 能力 来放宽这一限制,这些能力允许值被复制或丢弃, 也可以存储在全局存储中或用于定义全局存储模式.
结构体必须在模块内定义:
module 0x2::m {    struct Foo { x: u64, y: bool }    struct Bar {}    struct Baz { foo: Foo }    //                   ^ 注意:允许末尾有逗号}结构体不能递归定义,因此以下定义是无效的:
module 0x2::m {  struct Foo { x: Foo }  //              ^ 错误!Foo不能包含Foo}关于使用编号字段而非命名字段的位置结构体,请参阅 位置结构体 部分.
如上所述:默认情况下,结构体声明是线性且短暂的.因此,为了允许值进行某些操作(如复制,丢弃,存储到全局存储或用作存储模式),
可以通过用  has <能力>  标注来为结构体授予 能力 :
module 0x2::m {  struct Foo has copy, drop { x: u64, y: bool }}更多细节请参阅 结构体注解 部分.
结构体名称必须以大写字母 A 到 Z 开头.首字母之后,
结构体名称可以包含下划线 _ ,字母 a 到 z ,字母 A 到 Z 或数字 0 到 9.
module 0x2::m {  struct Foo {}  struct BAR {}  struct B_a_z_4_2 {}}这种以 A 到 Z 开头的命名限制是为了给未来语言特性留出空间.
未来可能会也可能不会取消此限制.
可以通过指定结构体名称后跟每个字段的值来创建(或”打包”)结构体类型的值:
module 0x2::m {  struct Foo has drop { x: u64, y: bool }  struct Baz has drop { foo: Foo }
  fun example() {    let foo = Foo { x: 0, y: false };    let baz = Baz { foo };  }}如果使用与字段同名的局部变量初始化结构体字段,可以使用以下简写形式:
module 0x2::m {  fun example() {    let baz = Baz { foo: foo };    // 等价于    let baz = Baz { foo };  }}这种情况有时被称为”字段名双关 “field name punning”.
通过模式匹配解构结构体
Section titled “通过模式匹配解构结构体”可以通过模式绑定或赋值来解构结构体值.
module 0x2::m {  struct Foo { x: u64, y: bool }  struct Bar { foo: Foo }  struct Baz {}
  fun example_destroy_foo() {    let foo = Foo { x: 3, y: false };    let Foo { x, y: foo_y } = foo;    //        ^ `x: x`的简写形式
    // 创建两个新绑定    //   x: u64 = 3    //   foo_y: bool = false  }
  fun example_destroy_foo_wildcard() {    let foo = Foo { x: 3, y: false };    let Foo { x, y: _ } = foo;
    // 由于 y 被绑定到通配符,只创建一个新绑定    //   x: u64 = 3  }
  fun example_destroy_foo_assignment() {    let x: u64;    let y: bool;    Foo { x, y } = Foo { x: 3, y: false };
    // 修改现有变量x和y    //   x = 3, y = false  }
  fun example_foo_ref() {    let foo = Foo { x: 3, y: false };    let Foo { x, y } = &foo;
    // 创建两个新绑定    //   x: &u64    //   y: &bool  }
  fun example_foo_ref_mut() {    let foo = Foo { x: 3, y: false };    let Foo { x, y } = &mut foo;
    // 创建两个新绑定    //   x: &mut u64    //   y: &mut bool  }
  fun example_destroy_bar() {    let bar = Bar { foo: Foo { x: 3, y: false } };    let Bar { foo: Foo { x, y } } = bar;    //             ^ 嵌套模式
    // 创建两个新绑定    //   x: u64 = 3    //   y: bool = false  }
  fun example_destroy_baz() {    let baz = Baz {};    let Baz {} = baz;  }}借用结构体及其字段
Section titled “借用结构体及其字段”可以使用 & 和 &mut 运算符创建对结构体或字段的引用.
以下示例包含一些可选类型标注(如 : &Foo )来演示操作类型.
module 0x2::m {  fun example() {    let foo = Foo { x: 3, y: true };    let foo_ref: &Foo = &foo;    let y: bool = foo_ref.y;  // 通过结构体引用读取字段    let x_ref: &u64 = &foo.x;
    let x_ref_mut: &mut u64 = &mut foo.x;    *x_ref_mut = 42;  // 通过可变引用修改字段  }}可以借用嵌套结构体的内部字段:
module 0x2::m {  fun example() {    let foo = Foo { x: 3, y: true };    let bar = Bar { foo };
    let x_ref = &bar.foo.x;  }}也可以通过结构体引用来借用字段:
module 0x2::m {  fun example() {    let foo = Foo { x: 3, y: true };    let foo_ref = &foo;    let x_ref = &foo_ref.x;    // 效果等同于 let x_ref = &foo.x  }}读取和写入字段
Section titled “读取和写入字段”如果字段是可复制的,可以通过解引用借用的字段来读取和复制字段值:
module 0x2::m {  fun example() {    let foo = Foo { x: 3, y: true };    let bar = Bar { foo: copy foo };    let x: u64 = *&foo.x;    let y: bool = *&foo.y;    let foo2: Foo = *&bar.foo;  }}点运算符可用于读取和复制结构体的任何可复制字段,无需显式借用和解引用:
module 0x2::m {  fun example() {    let foo = Foo { x: 3, y: true };    let x = foo.x;  // x == 3    let y = foo.y;  // y == true
    let bar = Bar { foo };    let foo2: Foo = *&bar.foo; // `Foo` 必须可复制    let foo3: Foo = bar.foo;   // 同上  }}点运算符可以链式使用以访问嵌套字段:
module 0x2::m {  fun example() {    let baz = Baz { foo: Foo { x: 3, y: true } };    let x = baz.foo.x; // x = 3;  }}此外,点语法还可用于修改字段.
module 0x2::m {  fun example() {    let foo = Foo { x: 3, y: true };    foo.x = 42;     // foo = Foo { x: 42, y: true }    foo.y = !foo.y; // foo = Foo { x: 42, y: false }    let bar = Bar { foo };            // bar = Bar { foo: Foo { x: 42, y: false } }    bar.foo.x = 52;                   // bar = Bar { foo: Foo { x: 52, y: false } }    bar.foo = Foo { x: 62, y: true }; // bar = Bar { foo: Foo { x: 62, y: true } }  }}点语法同样适用于结构体的引用:
module 0x2::m {  fun example() {    let foo = Foo { x: 3, y: true };    let foo_ref = &mut foo;    foo_ref.x = foo_ref.x + 1;  }}特权结构体操作
Section titled “特权结构体操作”大多数针对结构体类型 T 的操作只能在声明 T 的模块内执行:
- 结构体类型只能在定义该结构体的模块内被创建(“打包”)或销毁(“解包”)
- 结构体的字段只能在定义该结构体的模块内访问
遵循这些规则,若需要在模块外修改结构体,必须为其提供公共 API.本章末尾将提供相关示例.
但结构体 类型 对其他模块或脚本始终可见:
module 0x2::m {  struct Foo has drop { x: u64 }
  public fun new_foo(): Foo {    Foo { x: 42 }  }}module 0x2::n {  use 0x2::m;
  struct Wrapper has drop {    foo: m::Foo  }
  fun f1(foo: m::Foo) {    let x = foo.x;    //      ^ 错误!此处无法访问 `foo` 的字段  }
  fun f2() {    let foo_wrapper = Wrapper { foo: m::new_foo() };  }}注意结构体没有可见性修饰符(如 public 或 private).
如前文定义结构体所述,结构体默认是线性且临时的.这意味着它们不能被复制或丢弃. 该特性在建模现实资源(如货币)时非常有用,因为你不希望货币被复制或在流通中丢失.
module 0x2::m {  struct Foo { x: u64 }
  public fun copying_resource() {    let foo = Foo { x: 100 };    let foo_copy = copy foo; // 错误!'copy' 操作需要 'copy' 能力    let foo_ref = &foo;    let another_copy = *foo_ref // 错误!解引用需要 'copy' 能力  }
  public fun destroying_resource1() {    let foo = Foo { x: 100 };
    // 错误!函数返回时 foo 仍包含值    // 此销毁操作需要 'drop' 能力  }
  public fun destroying_resource2(f: &mut Foo) {    *f = Foo { x: 100 } // 错误!                        // 通过写入销毁旧值需要 'drop' 能力  }}要修复第二个示例( fun destroying_resource1 ),你需要手动 “解包” 资源:
module 0x2::m {  struct Foo { x: u64 }
  public fun destroying_resource1_fixed() {    let foo = Foo { x: 100 };    let Foo { x: _ } = foo;  }}请注意,你只能在定义资源的模块中解构资源.这一特性可以用来强制系统保持某些不变量,例如货币守恒.
另一方面,如果你的结构体不代表有价值的事物,可以添加 copy 和 drop 能力来获得一个可能更符合其他编程语言习惯的结构体值:
module 0x2::m {  struct Foo has copy, drop { x: u64 }
  public fun run() {    let foo = Foo { x: 100 };    let foo_copy = copy foo;    // ^ 这行代码复制了 foo,而 `let x = foo` 或    // ` let x = move foo` 都会移动 foo
    let x = foo.x;            // x = 100    let x_copy = foo_copy.x;  // x = 100
    // 当函数返回时, foo 和 foo_copy 都会被隐式丢弃  }}自语言版本2.0起
结构体可以声明为具有 位置字段 ,这些字段没有名称只有编号. 位置结构体的行为与常规结构体类似,只是提供了不同的语法,可能更适合字段较少的使用场景.
位置结构体的字段按出现顺序分配.在下面的示例中,字段 0 是 u64 类型,
字段 1 是 u8 类型:
module 0x2::m {  struct Pair(u64, u8);}位置结构体的能力声明在字段列表 之后 而非之前:
module 0x2::m {  struct Pair(u64, u8) has copy, drop;}对于纯类型标签( Move 代码中常用于 phantom 类型),也可以完全省略参数列表:
module 0x2::m {  struct TypeTag has copy, drop;}位置结构体的值创建和解构如下所示:
使用 PositionalStructs(参数) 语法:
module 0x2::m {  fun work() {    let value = Pair(1, true);    let Pair(number, boolean) = value;    assert!(number == 1 && boolean == true);  }}位置结构体的字段可以使用位置作为字段选择器来访问.例如在上面的代码中,
可以使用 value.0 和 value.1 来访问两个字段而无需解构 value .
自语言版本2.0起
模式可以使用 .. 表示法来匹配结构体或具名字段变体中未列出的剩余字段,
以及位置字段结构体或变体中开头或结尾被省略的字段.以下是一些示例:
module 0x2::m {  struct Foo{ x: u8, y: u16, z: u32 }  struct Bar(u8, u16, u32);
  fun foo_get_x(self: &Foo): u16 {    let Foo{y, ..} = self;    x  }
  fun bar_get_0(self: &Foo): u8 {    let Bar(x, ..) = self;    x  }
  fun bar_get_2(self: &Foo): u52 {    // 对于位置结构体,也可以将..    // 放在开头    let Bar(.., z) = self;    z  }}请注意,目前部分模式 ( partial patterns ) 不能用作赋值的左侧.
虽然可以使用 let Bar(x, ..) = v,但我们暂不支持 let x; Bar(x, ..) = v 这种写法.
在全局存储中存储资源
Section titled “在全局存储中存储资源”具有 key 能力的结构体可以直接保存到 持久化全局存储中.
存储在这些 key 结构体内部的所有值都必须具备 store 能力.详见 能力 和 全局存储 章节.
以下是两个简短的示例,展示如何使用结构体表示有价值的数据(如 Coin )
或更经典的数据(如 Point 和 Circle).
示例 1 :代币
Section titled “示例 1 :代币”module 0x2::m {  // 我们不希望代币被复制,因为那会导致"货币"重复,  // 因此不给该结构体赋予 'copy' 能力。  // 同样地,我们不希望程序员销毁代币,因此不给结构体赋予 'drop' 能力。  // 但我们*希望*模块用户能将此代币存入持久化全局存储,  // 所以赋予结构体 'store' 能力。该结构体只会存在于全局存储的其他资源中,  // 因此不赋予 'key' 能力。  struct Coin has store {    value: u64,  }
  public fun mint(value: u64): Coin {    // 需要对此函数设置某种访问控制,防止模块用户无限铸币    Coin { value }  }
  public fun withdraw(coin: &mut Coin, amount: u64): Coin {    assert!(coin.balance >= amount, 1000);    coin.value = coin.value - amount;    Coin { value: amount }  }
  public fun deposit(coin: &mut Coin, other: Coin) {    let Coin { value } = other;    coin.value = coin.value + value;  }
  public fun split(coin: Coin, amount: u64): (Coin, Coin) {    let other = withdraw(&mut coin, amount);    (coin, other)  }
  public fun merge(coin1: Coin, coin2: Coin): Coin {    deposit(&mut coin1, coin2);    coin1  }
  public fun destroy_zero(coin: Coin) {    let Coin { value } = coin;    assert!(value == 0, 1001);  }}示例 2 : 几何图形
Section titled “示例 2 : 几何图形”module 0x2::point {  struct Point has copy, drop, store {    x: u64,    y: u64,  }
  public fun new(x: u64, y: u64): Point {    Point {      x, y    }  }
  public fun x(p: &Point): u64 {    p.x  }
  public fun y(p: &Point): u64 {    p.y  }
  fun abs_sub(a: u64, b: u64): u64 {    if (a < b) {      b - a    }    else {      a - b    }  }
  public fun dist_squared(p1: &Point, p2: &Point): u64 {    let dx = abs_sub(p1.x, p2.x);    let dy = abs_sub(p1.y, p2.y);    dx*dx + dy*dy  }}module 0x2::circle {  use 0x2::point::{Self, Point};
  struct Circle has copy, drop, store {    center: Point,    radius: u64,  }
  public fun new(center: Point, radius: u64): Circle {    Circle { center, radius }  }
  public fun overlaps(c1: &Circle, c2: &Circle): bool {    let dist_squared_value = point::dist_squared(&c1.center, &c2.center);    let r1 = c1.radius;    let r2 = c2.radius;    dist_squared_value <= r1*r1 + 2*r1*r2 + r2*r2  }}