Sui Move Language - Structs

Sui Move Language - Structs

Everything we need to know about structs

·

6 min read

Hello Everyone..

Welcome to another day of exploring Web3 Engineering. Today we are gonna explore the structs on move language.

What are Structs ?

A struct is a user-defined data structure containing typed fields. Structs can store any non-reference, non-tuple type, including other structs. By default structs are linear and ephemeral (short lived). Move smart contracts uses structs to define objects on the blockchain.

What is an Object ?

An Object is a user defined datatype which is directly stored on the blockchain. The objects are defined using the struct data structure. Every object must have a key as a first property containing a unique global identifier UID. And we can define it using the helper function from the standard library. Sui's bytecode verifier ensures that new objects are always assigned fresh UIDs (identifiers are never re-used).

Syntax

They are defined using the struct keyword and the syntax is as follows:

public struct StructName {
    arg1: dataType1;
    arg2: dataType2;
    ...
}

public struct User {
    id: ID;
    age: u8;
    wallet_address: address;
}

Below we have the syntax on initialising a struct

let variable_name = StructName {args: values};

let user = User {
    id: 1,
    age: 12,
    wallet_address: @0x12;
}

Visibility

At the time of this article, Sui only has support for the struct to be public. In future there are plans to make them public(package) or internal. Event though the structs are declared public, the ability to create / destroy them is limited to the module they are defined in.

Ability

By default, a struct declaration is linear and ephemeral which means they can't be copied or destroyed. So to allow the value to be used in these ways (e.g., copied, dropped, stored in an object, or used to define a storable object), structs can be granted abilities by annotating them with has <ability>. The syntax is as follows

module a::m { 
    public struct Foo has copy, drop { 
        x: u64, y: bool 
    }
}

Struct Operations

  1. Creating - The syntax to create a struct is as follows

     module a::m {
         public struct Foo has drop { x: u64, y: bool }
         public struct Baz has drop { foo: Foo }
    
         fun example() {
             let foo = Foo { x: 0, y: false };
             let baz = Baz { foo: foo };
         }
     }
    
  2. Destroying - Struct values can be destroyed by binding or assigning them in patterns using similar syntax to constructing them.

     module a::m {
         public struct Foo { x: u64, y: bool }
         public struct Bar(Foo)
         public struct Baz {}
         public struct Qux()
    
         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 and 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 { x: 3, y: false });
             let Bar(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;
         }
    
         fun example_destroy_qux() {
             let qux = Qux();
             let Qux() = qux;
         }
     }
    
  3. Accessing Struct fields - Fields of a struct can be accessed using the dot operator . .

     public struct Foo { x: u64, y: bool }
     let foo = Foo { x: 3, y: true };
     let x = foo.x;  // x == 3
     let y = foo.y;  // y == true
    
  4. 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.

     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;        // borrowing a field by extending a reference to the struct
    
     let x_ref_mut: &mut u64 = &mut foo.x;
     *x_ref_mut = 42;            // modifying a field via a mutable reference
    
  5. Reading and Writing Fields - If you need to read and copy a field's value, you can then dereference the borrowed field

     let foo = Foo { x: 3, y: true };
     let bar = Bar(copy foo);
     let x: u64 = *&foo.x;
     let y: bool = *&foo.y;
     let foo2: Foo = *&bar.0;
    

Methods

As a syntactic convenience, some functions in Move can be called as "methods" on a value. This is done by using the . operator to call the function, where the value on the left-hand side of the . is the first argument to the function (sometimes called the receiver). The type of that value statically determines which function is called. This is an important difference from some other languages, where this syntax might indicate a dynamic call, where the function to be called is determined at runtime. In Move, all function calls are statically determined.

The method syntax is as follows

// Syntax
<expression> . <identifier> <[type_arguments],*> ( <arguments> )
// Example
coin.value();
*nums.borrow_mut(i) = 5;

use fun alias

Like a traditional use, a use fun statement creates an alias local to its current scope. This could be for the current module or the current expression block. However, the alias is associated to a type.

// Syntax
use fun <function> as <type>.<method alias>;

// Example
module b::example {
    use fun a::cup::cup_borrow as Cup.borrow;
    use fun a::cup::cup_value as Cup.value;
    use fun a::cup::cup_swap as Cup.set;

    fun example(c: &mut Cup<u64>) {
        let _ = c.borrow(); // resolves to a::cup::cup_borrow
        let v = c.value(); // resolves to a::cup::cup_value
        c.set(v * 2); // resolves to a::cup::cup_swap
    }
}

With that, we have covered on how to define, create, read, destroy structs and also some advance patterns on using structs. In the coming sections, let us start to write our first smart contract and then we can discuss on other concepts as well