Skip to content
🎉 Welcome to the new Aptos Docs! Click here to submit feedback!

Structs and Resources

A struct is a user-defined data structure containing typed fields. Structs can store any non-reference type, including other structs.

We often refer to struct values as resources if they cannot be copied and cannot be dropped. In this case, resource values must have ownership transferred by the end of the function. This property makes resources particularly well served for defining global storage schemas or for representing important values (such as a token).

By default, structs are linear and ephemeral. By this we mean that they: cannot be copied, cannot be dropped, and cannot be stored in global storage. This means that all values have to have ownership transferred (linear) and the values must be dealt with by the end of the program’s execution (ephemeral). We can relax this behavior by giving the struct abilities which allow values to be copied or dropped and also to be stored in global storage or to define global storage schemas.

Defining Structs

Structs must be defined inside a module:

module 0x2::m {
    struct Foo { x: u64, y: bool }
    struct Bar {}
    struct Baz { foo: Foo, }
    //                   ^ note: it is fine to have a trailing comma
}

Structs cannot be recursive, so the following definition is invalid:

module 0x2::m {
  struct Foo { x: Foo }
  //              ^ error! Foo cannot contain Foo
}

For positional structs which used numbered instead of named fields, see the positional structs section.

As mentioned above: by default, a struct declaration is linear and ephemeral. So to allow the value to be used with certain operations (that copy it, drop it, store it in global storage, or use it as a storage schema), structs can be granted abilities by annotating them with has <ability>:

module 0x2::m {
  struct Foo has copy, drop { x: u64, y: bool }
}

For more details, see the annotating structs section.

Naming

Structs must start with a capital letter A to Z. After the first letter, struct names can contain underscores _, letters a to z, letters A to Z, or digits 0 to 9.

module 0x2::m {
  struct Foo {}
  struct BAR {}
  struct B_a_z_4_2 {}
}

This naming restriction of starting with A to Z is in place to give room for future language features. It may or may not be removed later.

Using Structs

Creating Structs

Values of a struct type can be created (or “packed”) by indicating the struct name, followed by value for each field:

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

If you initialize a struct field with a local variable whose name is the same as the field, you can use the following shorthand:

module 0x2::m {
  fun example() {
    let baz = Baz { foo: foo };
    // is equivalent to
    let baz = Baz { foo };
  }
}

This is sometimes called “field name punning”.

Destroying Structs via Pattern Matching

Struct values can be destroyed by binding or assigning them patterns.

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;
    //        ^ shorthand for `x: x`
 
    // two new bindings
    //   x: u64 = 3
    //   foo_y: bool = false
  }
 
  fun example_destroy_foo_wildcard() {
    let foo = Foo { x: 3, y: false };
    let Foo { x, y: _ } = foo;
 
    // only one new binding since y was bound to a wildcard
    //   x: u64 = 3
  }
 
  fun example_destroy_foo_assignment() {
    let x: u64;
    let y: bool;
    Foo { x, y } = Foo { x: 3, y: false };
 
    // mutating existing variables x & y
    //   x = 3, y = false
  }
 
  fun example_foo_ref() {
    let foo = Foo { x: 3, y: false };
    let Foo { x, y } = &foo;
 
    // two new bindings
    //   x: &u64
    //   y: &bool
  }
 
  fun example_foo_ref_mut() {
    let foo = Foo { x: 3, y: false };
    let Foo { x, y } = &mut foo;
 
    // two new bindings
    //   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;
    //             ^ nested pattern
 
    // two new bindings
    //   x: u64 = 3
    //   y: bool = false
  }
 
  fun example_destroy_baz() {
    let baz = Baz {};
    let Baz {} = baz;
  }
}

Borrowing Structs and Fields

The & and &mut operator can be used to create references to structs or fields. These examples include some optional type annotations (e.g., : &Foo) to demonstrate the type of operations.

module 0x2::m {
  fun example() {
    let foo = Foo { x: 3, y: true };
    let foo_ref: &Foo = &foo;
    let y: bool = foo_ref.y;  // reading a field via a reference to the struct
    let x_ref: &u64 = &foo.x;
 
    let x_ref_mut: &mut u64 = &mut foo.x;
    *x_ref_mut = 42;  // modifying a field via a mutable reference
  }
}

It is possible to borrow inner fields of nested structs:

module 0x2::m {
  fun example() {
    let foo = Foo { x: 3, y: true };
    let bar = Bar { foo };
 
    let x_ref = &bar.foo.x;
  }
}

You can also borrow a field via a reference to a struct:

module 0x2::m {
  fun example() {
    let foo = Foo { x: 3, y: true };
    let foo_ref = &foo;
    let x_ref = &foo_ref.x;
    // this has the same effect as let x_ref = &foo.x
  }
}

Reading and Writing Fields

If a field is copyable, you can read and copy a field’s value by dereferencing the borrowed field:

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

The dot operator can be used to read and copy any copyable field of a struct without explicit borrowing and dereferencing:

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` must be copyable
    let foo3: Foo = bar.foo;   // same as the statement above
  }
}

Dot operators can be chained to access nested fields:

module 0x2::m {
  fun example() {
    let baz = Baz { foo: Foo { x: 3, y: true } };
    let x = baz.foo.x; // x = 3;
  }
}

Furthermore, the dot syntax can be used to modify fields.

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

The dot syntax also works via a reference to a struct:

module 0x2::m {
  fun example() {
    let foo = Foo { x: 3, y: true };
    let foo_ref = &mut foo;
    foo_ref.x = foo_ref.x + 1;
  }
}

Privileged Struct Operations

Most struct operations on a struct type T can only be performed inside the module that declares T:

  • Struct types can only be created (“packed”), destroyed (“unpacked”) inside the module that defines the struct.
  • The fields of a struct are only accessible inside the module that defines the struct.

Following these rules, if you want to modify your struct outside the module, you will need to provide public APIs for them. The end of the chapter contains some examples of this.

However, struct types are always visible to another module or script:

// m.move
module 0x2::m {
  struct Foo has drop { x: u64 }
 
  public fun new_foo(): Foo {
    Foo { x: 42 }
  }
}
// n.move
module 0x2::n {
  use 0x2::m;
 
  struct Wrapper has drop {
    foo: m::Foo
  }
 
  fun f1(foo: m::Foo) {
    let x = foo.x;
    //      ^ error! cannot access fields of `foo` here
  }
 
  fun f2() {
    let foo_wrapper = Wrapper { foo: m::new_foo() };
  }
}

Note that structs do not have visibility modifiers (e.g., public or private).

Ownership

As mentioned above in Defining Structs, structs are by default linear and ephemeral. This means they cannot be copied or dropped. This property can be very useful when modeling real world resources like money, as you do not want money to be duplicated or get lost in circulation.

module 0x2::m {
  struct Foo { x: u64 }
 
  public fun copying_resource() {
    let foo = Foo { x: 100 };
    let foo_copy = copy foo; // error! 'copy'-ing requires the 'copy' ability
    let foo_ref = &foo;
    let another_copy = *foo_ref // error! dereference requires the 'copy' ability
  }
 
  public fun destroying_resource1() {
    let foo = Foo { x: 100 };
 
    // error! when the function returns, foo still contains a value.
    // This destruction requires the 'drop' ability
  }
 
  public fun destroying_resource2(f: &mut Foo) {
    *f = Foo { x: 100 } // error!
                        // destroying the old value via a write requires the 'drop' ability
  }
}

To fix the second example (fun destroying_resource1), you would need to manually “unpack” the resource:

module 0x2::m {
  struct Foo { x: u64 }
 
  public fun destroying_resource1_fixed() {
    let foo = Foo { x: 100 };
    let Foo { x: _ } = foo;
  }
}

Recall that you are only able to deconstruct a resource within the module in which it is defined. This can be leveraged to enforce certain invariants in a system, for example, conservation of money.

If on the other hand, your struct does not represent something valuable, you can add the abilities copy and drop to get a struct value that might feel more familiar from other programming languages:

module 0x2::m {
  struct Foo has copy, drop { x: u64 }
 
  public fun run() {
    let foo = Foo { x: 100 };
    let foo_copy = copy foo;
    // ^ this code copies foo, whereas `let x = foo` or
    // `let x = move foo` both move foo
 
    let x = foo.x;            // x = 100
    let x_copy = foo_copy.x;  // x = 100
 
    // both foo and foo_copy are implicitly discarded when the function returns
  }
}

Positional Structs

Since language version 2.0

A struct can be declared to have positional fields, fields which are not named but numbered. Positional structs behave similar to regular structs, except providing a different syntax which may be more suitable for use cases with only a few fields.

Fields of positional structs are assigned in the order they appear. In the below example, field 0 is of type u64 and field 1 of type u8:

module 0x2::m {
  struct Pair(u64, u8);
}

Abilities for positional structs are declared after and not before the field list,

module 0x2::m {
  struct Pair(u64, u8) has copy, drop;
}

For pure type tags, often used for phantom types in Move code, the list of arguments can be also completely omitted:

module 0x2::m {
  struct TypeTag has copy, drop;
}

Values of positional structs are created and deconstructed as shown below: using PositionalStructs(arguments):

module 0x2::m {
  fun work() {
    let value = Pair(1, true);
    let Pair(number, boolean) = value;
    assert!(number == 1 && boolean == true);
  }
}

Fields of positional structs can be accessed using the position as a field selector. For example, in the above code example, value.0 and value.1 can be used to access the two fields without deconstructing the value. positional_struct.0.

Partial Patterns

Since language version 2.0

Patterns can use the .. notation to match any remaining, non-listed fields in structs or variants with named fields, and omitted fields at either the beginning or end of a struct or variant with positional fields. Here are some examples:

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 { 
    // For positional structs, one can also put the 
    // .. at the beginning.
    let Bar(.., z) = self;
    z
  }
}

Notice that partial patterns can currently not be used as the left-hand side of assignment. While one can use let Bar(x, ..) = v, we do not yet support let x; Bar(x, ..) = v.

Storing Resources in Global Storage

Structs with the key ability can be saved directly in persistent global storage. All values stored within those key structs must have the store ability. See the ability and global storage chapters for more detail.

Examples

Here are two short examples of how you might use structs to represent valuable data (in the case of Coin) or more classical data (in the case of Point and Circle).

Example 1: Coin

module 0x2::m {
  // We do not want the Coin to be copied because that would be duplicating this "money",
  // so we do not give the struct the 'copy' ability.
  // Similarly, we do not want programmers to destroy coins, so we do not give the struct the
  // 'drop' ability.
  // However, we *want* users of the modules to be able to store this coin in persistent global
  // storage, so we grant the struct the 'store' ability. This struct will only be inside of
  // other resources inside of global storage, so we do not give the struct the 'key' ability.
  struct Coin has store {
    value: u64,
  }
 
  public fun mint(value: u64): Coin {
    // You would want to gate this function with some form of access control to prevent
    // anyone using this module from minting an infinite amount of coins.
    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);
  }
}

Example 2: Geometry

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