Structures

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 Rogue.

Definition#

Struct definition is only allowed inside a method. 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:

method 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 method (just like you would access method 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. Rogue compiler checks recursive definitions and won't let you compile code like this:

method 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 method where they're defined.

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

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

Rogue 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 method can access its struct's fields. Outside of method fields are private.

Struct fields are only visible inside its method. Outside of this method (in script or another method) 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 method 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:

method 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 Rogue and sometimes you may need to destruct a structure without using its fields. For unused struct fields use _ - underscore:

method 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 method defines more than one struct.

method 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 method 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.