Structures

Structure is a custom type which contains complex data (or no data). It can be described as a simple key-value storage where key is a name of property and value is what's stored. Defined using keyword struct. Struct can have up to 4 abilities, they are specified with type definition.

Struct is the only way to create custom type in Move.

Definition

Struct definition is only allowed inside a module. It begins with keyword struct, followed by name and curly braces, where struct fields are defined:

struct NAME {
    FIELD1: TYPE1,
    FIELD2: TYPE2,
    ...
}

Look at these examples of struct definitions:

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

Max number of fields in one struct is 65535.

Every defined struct becomes new a type. This type can be accessed through its module (just like you would access module functions):

M::MyStruct;
// or
M::Example;

Recursive definition

Short as never:

Recursive struct definition is impossible.

You are allowed to use another struct as type but you can't recursively use the same struct. Move compiler checks recursive definitions and won't let you compile code like this:

module M {
    struct MyStruct {

        // WON'T COMPILE
        field: MyStruct
    }
}

Create new struct

To use this type you need to create its instance.

New instances can only be created inside the module where they're defined.

To create new instance use it's definition, but instead of passing types pass values of these types:

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

Move also allows you to create new instances shorter - by passing variable name which matches struct's field (and type!). We can simplify our new_country() method using this rule:

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

To create an empty struct (with no fields) simply use curly braces:

public fun empty(): Empty {
    Empty {}
}

Access struct fields

Structs would have been almost useless if we hadn't had a way to access their fields (though you can create struct without fields).

Only module can access its struct's fields. Outside of module fields are private.

Struct fields are only visible inside its module. Outside of this module (in script or another module) it's just a type. To access struct's fields use . (dot) notation:

// ...
public fun get_country_population(country: Country): u64 {
    country.population // <struct>.<property>
}

If nested struct type is defined in the same module it can be accessed in similar manner which can be generally described as:

<struct>.<field>
// and field can be another struct so
<struct>.<field>.<nested_struct_field>...

Destructing structures

To destruct a struct use let <STRUCT DEF> = <STRUCT> syntax:

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

You should remember that unused variables are prohibited in Move and sometimes you may need to destruct a structure without using its fields. For unused struct fields use _ - underscore:

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

Destructuring may not seem important right now. But remember it - it will play a huge part when we get to resources.

Implementing getter-functions for struct fields

To make struct fields readable outside, you need to implement methods which will read these fields and pass them as return values. Usually the getter method is called the same way as struct's field but it may cause inconvenience if your module defines more than one struct.

module Country {

    struct Country {
        id: u8,
        population: u64
    }

    public fun new_country(id: u8, population: u64): Country {
        Country {
            id, population
        }
    }

    // don't forget to make these methods public!
    public fun id(country: &Country): u8 {
        country.id
    }

    // don't mind ampersand here for now. you'll learn why it's 
    // put here in references chapter 
    public fun population(country: &Country): u64 {
        country.population
    }

    // ... fun destroy ... 
}

By making getters we've allowed module users access fields of our struct:

script {
    use {{sender}}::Country as C;
    use 0x1::Debug;

    fun main() {
        // variable here is of type C::Country
        let country = C::new_country(1, 10000000);

        Debug::print<u8>(
            &C::id(&country)
        ); // print id

        Debug::print<u64>(
            &C::population(&country)
        );

        // however this is impossible and will lead to compile error
        // let id = country.id;
        // let population = country.population.

        C::destroy(country);
    }
}

Now you know how to define custom type - struct, but by default its functionality is limited. In the next chapter you will learn about abilities - a way to define how values of this type can be manipulated and used.