Skip to main content

Struct

A struct is similar to an entity, but its instances exist in memory, not in a database.

Structs have the following characteristics:

  • Attributes are immutable by default and only mutable when declared with a mutable keyword
  • An attribute might have a default value that's used if the attribute isn't specified during construction
  • Structs get deleted from memory implicitly by a garbage collector
Example
struct user {
name: text;
address: text;
mutable balance: integer = 0;
}

Create structs

Employ the struct name as a function to create instances, specifying attribute values as arguments.

Example
val u = user(name = 'Bob', address = 'New York');

Struct-copy of entities and objects

You can use .to_struct()to create a struct-copy of an entity or an object.

Example
entity user{
name;
address: text;
}

val u = user @ {.name == 'Bob'};
val s = u.to_struct(); // returns struct <user>

Create entities from structs

Conveniently create entities directly from struct<entity> values.

Example
create user(s); // s is struct<user>

Same rules as for the create expression apply: no need to specify attribute name if you can resolve it implicitly by name or type:

val name = 'Bob';
val address = 'New York';
val u = user(name, address);
val u2 = user(address, name); // Order does not matter - same struct value is created.

Access struct attributes

You can use . dot notation to access struct attributes.

Example
print(u.name, u.address);

Safe access for nullable structs

You can use the safe-access operator ?. to read or modify attributes of a nullable struct.

Example
val u: user? = find_user('Bob');
u?.balance += 100; // no-op if 'u' is null

Struct<mutable T>

Struct<mutable T> designates a struct where all attributes are mutable, enabling modifications after creation.

tip

When working with functions that require a substantial number of arguments, it's recommended to use structs to enhance code organization and readability. This approach promotes better code maintainability and reduces the likelihood of errors arising from argument order confusion.

Consider the following example function that creates a project:

function create_project(
name: text,
description: text,
rules: text,
public_name: text
) {
...
}

While this function is straightforward, the potential for argument order mix-ups exists. To address this concern, we can introduce a dedicated struct to encapsulate the function's arguments:

struct create_project_arguments {
name: text,
description: text,
rules: text,
public_name: text
};

By defining this struct, we can now utilize it to create a type-safe and self-documenting function parameter:

function create_project(project_arguments: create_project_arguments) {
// ... Project creation logic
}

To invoke this function, we can instantiate the create_project_arguments struct and assign values to its properties:

val arguments = create_project_arguments(
name: "test_project",
description: "my project description",
rules: "",
public_name: "my fancy test project"
);

create_project(arguments);

Struct<mutable T>

struct<mutable T> represents a struct where all attributes are mutable, allowing for modification after creation.

Convert entities and objects to mutable structs

Use .to_mutable_struct() to create a mutable struct representation:

val u = user @ { .name == 'Bob' };
val s = u.to_mutable_struct(); // will return a struct<mutable user>

Convert mutable and immutable structs

  • Employ .to_mutable() to convert a struct<T> to a struct<mutable T>.
  • Use .to_immutable() to convert a struct<mutable T> to a struct<T>.
val s = u.to_struct();
val mut = s.to_mutable();
val imm = mut.to_immutable();

struct<operation>

The type struct<operation> defines a struct that has the same attributes as a given operations parameters:

Example
operation add_user(name: text, rating: integer) {
// ...
}

query can_add_user(user: struct<add_user>) {
if (user.name == '') return false;
if (user.rating < 0) return false;
return true;
}

Functions available for all struct types:

FunctionDescription
T.from_bytes(byte_array): TDecode from a binary-encoded gtv. Same as T.from_gtv(gtv.from_bytes(x)).
T.from_gtv(gtv): TDecode from a gtv.
T.from_gtv_pretty(gtv): TDecode from a pretty-encoded gtv.
.to_bytes(): byte_arrayEncode in binary format. Same as .to_gtv().to_bytes().
.to_gtv(): gtvConvert to a gtv.
.to_gtv_pretty(): gtvConvert to a pretty gtv.
.to_gtx_operation(): gtx_operationConverts a struct<operation> to gtx_operation.
.to_test_op(): rell.test.opConverts a gtx_operation to a rell.test.op.

Enhanced examples for struct usage

struct gtx_operation

Encapsulates individual operations within a transaction.

struct gtx_operation {
name: text;
args: list<gtv>;
}

struct gtx_transaction_body

Defines the core content of a transaction, excluding signatures.

struct gtx_transaction_body {
blockchain_rid: byte_array;
operations: list<gtx_operation>;
signers: list<gtv>;
}

struct gtx_transaction

Represents a complete transaction, including signatures.

struct gtx_transaction {
body: gtx_transaction_body;
signatures: list<gtv>;
}

struct rell.test.keypair

Facilitates test keypair generation and usage.

struct rell.test.keypair {
priv: byte_array; // private key, must be 32 bytes
pub: byte_array; // public key, must be 33 bytes
}