Ion Fusion Documentation
Release 0.38a1-SNAPSHOT (2026-04-16T19:45:37.790Z)

Module /fusion/struct

Operations for structs.

A struct is an unordered collection of values, keyed by strings. Since Fusion structs are based on Ion structs, these collections are multi-maps: the same key can map to multiple values, or even multiple mappings to the same value. The elements of a struct are its values, but not the associated field names.

Because structs are unordered, operations that iterate over field keys or values do not guarantee a repeatable order of iteration: the order may change at any time, even between successive procedure calls. This (lack of) contract most commonly affects unit tests, which must be careful to compare actual and expected values using order-insensitive comparisons.

Structs come in two concrete types: immutable and mutable. The predicates is_immutable_struct and is_mutable_struct distinguish between them.

Creating Structs

In standard Fusion, Ion struct syntax denotes immutable values, treating field names as literals and field values as expressions. Thus {} denotes an immutable struct of size zero, and {f:[x]} denotes an immutable struct of size 1 holding a list whose only element is the value of the variable x. The value of a struct literal is immutable even when some child values are evaluated at run-time, but elements of such a struct may be mutable. Quoted forms are also immutable; in (quote {f:x}) the struct's sole element is the symbol 'x'.

The procedure struct works like a struct literal, creating an immutable struct from names and elements:

(struct "name" "Steve" "age" 29)  =>  {name:"Steve", age:29}

The procedure mutable_struct similarly creates mutable structs:

(mutable_struct "name" "Taylor" "age" 17)  =>  {name:"Taylor", age:17}

Note that the default "ionization strategy" renders all struct types the same, so the results look similar even though the values have different types. Eventually the application will be able to control this strategy; these defaults are designed to allow a Fusion developer to construct data in various combinations of mutability (etc.) and output it as "normal" Ion data.

struct_zip and mutable_struct_zip provide another way to create (immutable and mutable) structs. They accept two lists containing the names and elements:

(struct_zip ["name", "age"] ["Doug", 38])  =>  {age:38, name:"Doug"}

Modifying Structs

Fusion provides data modification operators that have a functional style, even when the modification involves mutation. The functional and mutating variants have the same signature and operate over both immutable and mutable types; in general they are interchangable when used in a functional style.

Changing the key-value mappings of a struct is performed via put and put_m, which guarantee that the result has only a single mapping for the key:

(put {a:1, b:9, b:12} "b" 11)  => {a:1, b:11}

The procedures remove_keys and retain_keys functionally eliminate fields from a struct without modifying the input; remove_keys_m and retain_keys_m modify the input when possible. struct_merge and struct_merge_m combine the fields from two structs.

Iterating Struct Fields

struct_iterator creates a multi-valued iterator over a struct's key-value pairs. The procedure struct_do iterates the name/value pairs within a struct, but since the input isn't modified one must use side effects.

More general collection operations like any and find can be applied to structs, in which case the field names are ignored while the field values are iterated.

. procedure
(. value key ...)

Traverses a "path" through a data structure, folding the value through each key in turn. When the value is void, it is returned immediately and any further keys are not applied. If a key is a procedure, it must accept one argument; the procedure is applied to the current value, and the result becomes the value for the next key. Otherwise, the key and value are passed to elt to get the next value.

(. [0, 1] 0)         =>  0
(. (sexp 0 1) 1)     =>  1
(. {f:2} "f")        =>  2
(. {f:3} (quote f))  =>  3

(. [0, 1, 2, 3] size)               =>  4
(. (sexp 0 1 2 3) head)             =>  0
(. (sexp 0 1 2 3) tail)             =>  (1 2 3)
(. (sexp 0 1 2 3) tail tail head)   =>  2

Since . is a procedure, field names must be quoted or else they will be evaluated as a variable reference:

(. {f:2} f)        => ERROR: Unbound variable reference
(let [(g "f")]
  (. {f:2} g))     => 2
any procedure
(any pred collection)

Applies the one-argument predicate pred to the elements of collection; the first time pred returns a truthy value that truthy value is returned and no more elements are visited. If no call returns a truthy value, then the result is that of the final predicate call, or false if the collection is empty.

When collection is a sequence, the elements are visited in order, and the application of pred to the final element of the sequence is in tail position.

do procedure
(do proc collection)

Applies the one-argument procedure proc to the elements of collection, ignoring any results. Returns void.

When collection is a sequence, the elements are visited in order.

See also: struct_do

element procedure
(element collection key)

Returns an element within a collection. The collection must be a non-null, non-empty list, sexp, or struct. The key must have a type appropriate for the collection: an int for lists or sexps, a string or symbol for structs.

(element [0, 1] 0)         =>  0
(element (sexp 0 1) 1)     =>  1
(element {f:2} "f")        =>  2
(element {f:3} (quote f))  =>  3

Since element is a procedure, field names must be quoted or else they will be evaluated as a variable reference:

(element {f:2} f)        => ERROR: Unbound variable reference
(let [(g "f")]
  (element {f:2} g))     => 2

An exception is raised if the collection has an unsupported type, if the key isn't appropriate for the collection, or if the key doesn't identify an element within the collection.

(element [0, 1] 2)       =>  ERROR
(element [0, 1] "2")     =>  ERROR
(element {f:2} "g")      =>  ERROR
elt procedure
(elt collection key)

Returns an element within a collection, being lenient. The collection must be a list, sexp, struct, or void. The key must have a type appropriate for the collection: an int for lists or sexps, a string or symbol for structs.

(elt [0, 1] 0)         =>  0
(elt (sexp 0 1) 1)     =>  1
(elt {f:2} "f")        =>  2
(elt {f:3} (quote f))  =>  3

If the collection is empty, null, or void, the result is void. If the key isn't appropriate for the collection, or if the key doesn't identify an element within the collection, the result is void.

(elt null.list 0)    =>  void
(elt [0, 1] 2)       =>  void
(elt [0, 1] "2")     =>  void
(elt {f:2} "g")      =>  void

Since elt is a procedure, field names must be quoted or else they will be evaluated as a variable reference:

(elt {f:2} f)        => ERROR: Unbound variable reference
(let [(g "f")]
  (elt {f:2} g))     => 2

An exception is raised if the collection has an unsupported type.

every procedure
(every pred collection)

Applies the one-argument predicate pred to the elements of collection; the first time pred returns an untruthy value that untruthy value is returned and no more elements are visited. If no call returns an untruthy value, then the result is that of the final predicate call, or true if the collection is empty. When collection is a sequence, the elements are visited in order, and the application of pred to the final element of the sequence is in tail position.

find procedure
(find pred collection)

Applies the one-argument predicate pred to each element of collection; the first time pred returns a truthy value that element is returned. If no such element is found, the result is void.

When collection is a sequence, the elements are visited in order.

When collection is a struct, the "elements" of the collection are its values (as opposed to its key-value pairs): the predicate will be applied on each value, and the result is either one of those values or void.

has_key procedure
(has_key collection key)

Determines whether a collection has a mapping for a given key. When has_key returns true, then (element collection key) will succeed.

Note that the keys of a sequence are the zero-based integer indices of the elements within the sequence, not the elements themselves.

(has_key {f:12} "f")           ==> true
(has_key [3,true,2014T] 0)     ==> true
(has_key [3,true,2014T] 3)     ==> false
(has_key [3,true,2014T] null)  ==> false
is_collection procedure
(is_collection value)

Determines whether value is a collection (struct, list, or sexp), returning true or false.

is_empty procedure
(is_empty collection)

Returns true if the size of the collection is zero, otherwise returns false.

is_immutable_struct procedure
(is_immutable_struct value)

Determines whether value is an immutable struct, returning true or false.

is_mutable_struct procedure
(is_mutable_struct value)

Determines whether value is a mutable struct, returning true or false.

is_struct procedure
(is_struct value)

Determines whether value is a struct, returning true or false.

mutable_struct procedure
(mutable_struct name value ... ...)

Constructs a mutable, non-null struct from alternating strings and values. Each name is a non-empty string or symbol denoting a field name, and the following value is the field's value. Names may be repeated to produce repeated (multi-mapped) fields.

mutable_struct_zip procedure
(mutable_struct_zip names values)

Constructs a mutable struct from a list of field names and a list of values. If the lists have unequal lengths, only the first n elements will be used where n is the shorter length.

The names must be non-empty strings or symbols.

(struct_zip ["f", "g"] [1, 2])  => {f:1,g:2}
(struct_zip ["f", "f"] [1, 2])  => {f:1,f:2}
(struct_zip ["f"] [1, 2])       => {f:1}
none procedure
(none pred collection)

Applies the one-argument predicate 'pred' to the elements of collection. Returns false if the predicate returns a truthy value for any element, true if none do, and true if the collection is empty.

put procedure
(put struct key value)

Functionally modifies a field of a struct, returning a new struct of the same type. Any existing fields (including repeats) named by the key are replaced by a single field with the value.

(define s {a:1, b:2, b:3})

(put s "a" 4)    ==> {b:2,b:3,a:4}
(put s "b" 5)    ==> {b:5,a:1}
(put s "c" 6)    ==> {b:2,b:3,c:6,a:1}
s                ==> {b:2,b:3,a:1}
put_m procedure
(put_m struct key value)

Modifies a field of a struct, mutating the struct when possible and returning a struct of the same type. Any existing fields (including repeats) named by the key are replaced by a single field with the value.

When given an immutable struct, put_m behaves identically to put:

(define s {a:1, b:2, b:3})

(put_m s "a" 4)    ==> {b:2,b:3,a:4}
(put_m s "b" 5)    ==> {b:5,a:1}
(put_m s "c" 6)    ==> {b:2,b:3,c:6,a:1}
s                  ==> {b:2,b:3,a:1}

When given a mutable struct, the struct is mutated:

(define s (mutable_struct "a" 1 "b" 2 "b" 3))

s                ==> {b:2,b:3,a:1}
(put_m s "a" 4)  ==> {b:2,b:3,a:4}
(put_m s "b" 5)  ==> {b:5,a:4}
(put_m s "c" 6)  ==> {b:5,c:6,a:4}
s                ==> {b:5,c:6,a:4}
remove_keys procedure
(remove_keys struct name ...)

Returns a struct derived from struct without fields with the given names. If the input is null.struct then the result is null.struct. The result will have the same annotations as struct.

remove_keys_m procedure
(remove_keys_m struct name ...)

Returns a struct similar to struct without fields with the given names. The result may share structure with the input, which may be mutated. If the input is null.struct then the result is null.struct. The result will have the same annotations as struct.

retain_keys procedure
(retain_keys struct name ...)

Returns a struct derived from struct with only fields with the given names. If the input is null.struct then the result is null.struct. The result will have the same annotations as struct.

retain_keys_m procedure
(retain_keys_m struct name ...)

Returns a struct similar to struct with only fields with the given names. The result may share structure with the input, which may be mutated. If the input is null.struct then the result is null.struct. The result will have the same annotations as struct.

size procedure
(size collection)

Returns the number of elements in the collection. The size of null.list (etc.) is zero. If collection is an improper sexp, an exception is thrown.

Warning: Computing the size of an sexp takes linear time, since it must traverse the linked list of pairs to count elements.

struct procedure
(struct name value ... ...)

Constructs an immutable, non-null struct from alternating strings and values. Each name is a non-empty string or symbol denoting a field name, and the following value is the field's value. Names may be repeated to produce repeated (multi-mapped) fields.

struct_do procedure
(struct_do proc struct)

Iterates the fields of struct for side-effects, applying proc to each name/value field. Returns struct. The proc must accept two arguments, a field name symbol and a value; any results from applying the procedure are ignored.

See also: do

struct_iterator procedure
(struct_iterator struct)

Returns an iterator over the content of struct. Calls to iterator_next will return two results representing a single field: the field's name (as a symbol) and the field's value.

(define (show struct)
  (let [(iter (struct_iterator struct))]
    (let loop []
      (when (iterator_has_next iter)
        (let_values [((k v) (iterator_next iter))]
          (displayln k " --> " v)
          (loop))))))
(show {foo:"bar", hello:"goodbye", yes:false})

This code displays:

yes --> false
hello --> goodbye
foo --> bar
struct_merge procedure
(struct_merge struct1 struct2)

Returns a struct that has all the name-value elements of both arguments. This will result in repeated fields if the names overlap or if one of the arguments has repeats. The result has the same type as the first argument.

(struct_merge {a:1} {a:1}) ==> {a:1, a:1}
(struct_merge {a:1} {b:2}) ==> {a:1, b:2}
struct_merge_m procedure
(struct_merge_m struct1 struct2)

Returns a struct that has all the name-value elements of both arguments, mutating the first argument when possible. This will result in repeated fields if the names overlap or if one of the arguments has repeats. The result has the same type as the first argument.

struct_unzip procedure
(struct_unzip struct)

Deconstructs a struct and returns two values: a list of field names (as symbols) and a list of associated values. This is the inverse of struct_zip.

(let_values [((keys vals) (struct_unzip {a:1, b:2}))]
  (struct_zip keys (map - vals)))
=>
{b:-2,a:-1}

This procedure returns multiple results, not a pair or sequence, so it must be called from a context that expects that, like a let_values clause with two bound identifiers.

struct_zip procedure
(struct_zip names values)

Constructs an immutable struct from a list of field names and a list of values. If the lists have unequal lengths, only the first n elements will be used where n is the shorter length.

The names must be non-empty strings or symbols.

(struct_zip ["f", "g"] [1, 2])  => {f:1,g:2}
(struct_zip ["f", "f"] [1, 2])  => {f:1,f:2}
(struct_zip ["f"] [1, 2])       => {f:1}