Polyglot
Languages MATLAB structures
MATLAB § structures

Structures

A struct is a MATLAB record: a value with named fields, each of which holds an array of any class. Fields are added by assignment, accessed by dot notation, and enumerated with fieldnames. A struct array is an array whose elements are structs sharing the same field names. Structs are the conventional MATLAB record type — the analogue of C structs and Python dataclasses — and are everywhere in interfaces with the standard library, in figure properties, in optimisation options, and in user code that does not (yet) merit a class.

Constructing a struct

The principal forms are literal and constructor:

% Literal — by assignment to fields
s.name = "Ada";
s.birthYear = 1815;
s.skills = ["analysis", "programming"];

% Constructor
s2 = struct('name', "Ada", 'birthYear', 1815, 'skills', ["analysis", "programming"]);

The two forms are equivalent. The struct constructor accepts name-value pairs and is the most common form in code that constructs structs programmatically. A field assignment creates the field if it does not already exist; a read of a non-existent field is an error.

Field access

The dot operator selects a field:

s.name              % "Ada"
s.birthYear         % 1815
s.skills(2)         % "programming"

The dynamic field reference s.(expr) evaluates expr as a string and uses the result as a field name; it permits programmatic access:

fname = "birthYear";
s.(fname)           % 1815

for f = fieldnames(s).'      % returns a cell column; transpose for iteration
    name = f{1};
    fprintf("%s = ", name)
    disp(s.(name))
end

The dynamic form is the right way to write code whose field names come from a variable; it should be preferred over eval for that purpose.

Struct arrays

A struct array is a 1-d or multi-d array of structs that share the same field names. The most reliable constructor is the assigned form:

arr(1).name = "Ada";
arr(1).year = 1815;
arr(2).name = "Grace";
arr(2).year = 1906;
arr(3).name = "Donald";
arr(3).year = 1938;

size(arr)           % [1 3]
class(arr)          % 'struct'
{arr.name}          % {"Ada", "Grace", "Donald"}
[arr.year]          % [1815 1906 1938]

The constructor form with cell-valued arguments builds a struct array directly:

arr = struct('name', {"Ada", "Grace", "Donald"}, ...
             'year', {1815, 1906, 1938});

A cell as an argument to struct is interpreted as the field values across the struct array. A scalar argument (not a cell) is replicated. The rule is a small surprise: struct('x', {1 2 3}) builds a 1×3 struct array, but struct('x', [1 2 3]) builds a 1×1 struct whose x field is [1 2 3].

The comma-separated-list form arr.name produces, for a length-N struct array, N separate values — exactly the same expansion idiom as cells with {:}. Brackets gather them: {arr.name} builds a cell, [arr.year] concatenates them.

Enumerating fields

fieldnames(s) returns a column cell of strings; isfield(s, 'name') tests presence; rmfield(s, 'name') returns a copy with the field removed; orderfields(s) reorders fields alphabetically (or by an explicit permutation).

fieldnames(s)
% 3×1 cell:
%   'name'
%   'birthYear'
%   'skills'

isfield(s, 'name')           % true
s2 = rmfield(s, 'skills');

The number of fields is numel(fieldnames(s)); the size of the struct array is size(s) — the two are unrelated.

Nested structs

Structs nest arbitrarily. Field access uses chained dots; the dynamic form chains likewise:

person.name = "Ada";
person.address.city = "London";
person.address.year = 1815;

person.address.city                 % "London"
person.address.(fname_var)         % dynamic-field on the inner struct

A common pattern in standard-library functions is the options struct: a function takes a single struct whose fields configure its behaviour. The optimset, odeset, and statset functions in their respective toolboxes build such structs.

Practical idioms

Storing per-record state. A struct array is the right container for a small, homogeneous list of records — say, a few hundred sensor readings:

for k = 1:numel(arr)
    arr(k).processed = process(arr(k).raw);
end

For larger or more heterogeneous tabular data, a table (the next page) is usually clearer.

Bundling outputs. A function with many outputs can return a single struct rather than a long argument list:

function result = analyse(x)
    result.mean = mean(x);
    result.std = std(x);
    result.n = numel(x);
    result.histogram = histcounts(x, 20);
end

The caller writes r = analyse(x); r.mean and is spared a long [mu, sigma, n, h] = analyse(x) call. The convention is widespread.

Configuration. A struct is a natural configuration record. The defaults are set once; the caller overrides specific fields. The helper setfield(s, 'name', value) is the function-form equivalent of s.name = value and is useful for programmatic construction.

struct vs class

A classdef class (the OOP page) offers methods, inheritance, property validation, and explicit handle-vs-value semantics. A struct offers none of these. For data with simple structure and no behaviour, a struct is the lighter and more idiomatic choice; for anything with invariants, behaviour, or polymorphism, a class is more appropriate. The path is one-way: a struct can be converted to a class, but a class instance is not a struct.

A note on cell-valued struct fields

A struct field may itself be a cell. To assign a single cell to a field (rather than letting struct(...) interpret it as the per-element values), wrap it once:

s = struct('x', {{1 2 3}});       % s.x is a 1×3 cell
size(s)                            % [1 1]
class(s.x)                         % 'cell'

% Compare:
t = struct('x', {1 2 3});         % a 1×3 struct array, each with scalar x
size(t)                            % [1 3]

The doubled brace is the idiomatic and somewhat ugly fix for this pitfall.