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]
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:
- Permission. The actual permission that governs field access is derived from both the field's and struct's permissions using a mechanism called viewpoint adaptation. If unspecified, mut is assumed, which effectively means the struct value's permission governs access.
Each field must be given a unique name.
However, names used in one struct will not be confused with same-named
fields in other types, nor with variable or function names.
Field names that begin with an underscore are considered pr\ivate. Private fields can only be accessed by the struct's methods. Fields should be private if we don't want logic outside the struct's methods to depend on this implementation detail or be able to change its value directly. This is particularly important when the field's value should be subject to additional invariant constraints that are properly enforced by the struct's methods.
The anonymous name _ may be used for any data segment of the struct that we don't want or need access to.
- Type. This can be any type: numeric, Bool, an array, some other struct, etc. The name Self may be used to refer to the struct type currently being defined. There is one important restriction: A field's type may not be the same as the struct type we are declaring (or any recursive variation on this). A field's type, however, may be a reference to the struct type.
- Default value. The default value must be a constant. Specifying default values makes initialization of a newly created struct more convenient.
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:
- The constructor must specify a value for every field lacking a default value.
- Only the type's methods may use the struct constructor, if the struct has any private field lacking a default value.
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.
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.
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.
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.
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.