A struct holds one or more named values (fields), each with its own type. The struct's fields are located together in memory and can be individually accessed using the field's name.

Let's illustrate by declaring a new struct type:

struct Complex:
  real f32
  imag f32

This defines a new struct type called Complex that holds two fields of data. The fields named real are both 32-bit floating point numbers.

Variables can be declared and initialized using this new type, each with its own values in these two fields:

imm comp1 = Complex[real: 3.2, imag: 0.0]
mut comp2 = Complex[real: 0.0, imag: 1.0]

This page focuses on using structs to hold field-oriented data. Subsequent pages will enrich what can be done with a struct by adding methods, inheritance, or traits.

Declaration

Before we can make use of a specific data structure, we need to declare it, giving it a name and specifying its fields:

struct Point:
    x f32 = 0.
    y f32 = 0.

The struct's declared name is effectively new, custom-defined type. This type name can be used to declare any number of variables containing values of this type (e.g., imm pt1 Point).

Field declarations look like variable declarations, specifying the field's:

Initialization

The initial value for any struct value is always built (explicitly or implicitly) using a struct constructor: the struct name followed by comma-separated field values in square brackets. Values may be marked with field names or just listed in declared order:

imm pt = Point [x: 3., y: 4.]    // Using field names
imm pt2 = Point [3., 4.]         // When field names are omitted, order matters.
imm pt3 = Point[]                // Uses x,y's declared default values of 0.

Several constraints apply to struct constructors:

It is also possible to initialize a new struct value by using one of the struct's defined initializer methods instead. Under the covers, initializer methods also use the struct constructor to initialize the struct's value.

Copying

Like numbers, struct values may be passed around between variables and functions:

fn main():
	imm pt1 = Point[]
	pt1 = change(pt1)

fn change(mut pt Point):
	pt = Point[1., 2.]
	pt

Copies of the data structures are made as they are passed around (e.g. into and returned by change). Any changes made to a copy won't change the original struct value. This may not always be the behavior that you want, though. If a struct is too big to pass around efficiently, or you want changes to apply to the source structure, make use of references to a struct instead.

Field Access

Use the . operator to access a struct field's value, specifying the struct value on the left and the field name on the right:

imm dist = (pt.x*pt.x + pt.y*pt.y).sqrt

A field's value may also be altered in this way:

pt.y = pt.x

The compiler applies field privacy and permission constraints to prevent invalid field accesses. A private field can only be accessed by the type's methods. Changing of a field's value is only allowed if the struct/field permissions allow mutability.

It is also possible to use the & operator to borrow a reference to a specific field within a struct value.

Comparing

It is not possible to check whether two structs have the same value if the struct defines only fields. However, two struct values may be compared if the struct implements the comparison methods. Also, it is possible to perform partial matching of a struct against some pattern.

Opaque Types

A struct declared with no fields is called an opaque type. It is typically used when we want to be able to point to data whose structure is a mystery (built built and managed by some external system). Since its content and size is unknown, one cannot create a new value of this type, pass such a value around, or directly access its fields. However, it is possible to pass around a reference to an opaque-typed value which some external function has created, as well as invoke any methods defined for it.

_