# Structs and records

> Choose value types, records, record structs, stack-only structs, and columnar records.

Canonical: https://alice-lang.org/documentation/language/records/

[Documentation index](https://alice-lang.org/documentation/llms.txt) · [Complete book](https://alice-lang.org/llms-full.txt)

## Named data and equality

```alice
record Point(x : Int32, y : Int32)
a = Point.new(2, 3)
puts a.x + a.y
puts a == Point.new(2, 3)
record struct Vec(x : Float64, y : Float64)
  def length : Float64
    Math.Sqrt(x * x + y * y)
  end
end
puts Vec.new(3.0, 4.0).length
```

Expected output:

```text
5
true
5
```

[Edit and run in the Alice playground](https://playground.alice-lang.org/#code=u.cmVjb3JkIFBvaW50KHggOiBJbnQzMiwgeSA6IEludDMyKQphID0gUG9pbnQubmV3KDIsIDMpCnB1dHMgYS54ICsgYS55CnB1dHMgYSA9PSBQb2ludC5uZXcoMiwgMykKcmVjb3JkIHN0cnVjdCBWZWMoeCA6IEZsb2F0NjQsIHkgOiBGbG9hdDY0KQogIGRlZiBsZW5ndGggOiBGbG9hdDY0CiAgICBNYXRoLlNxcnQoeCAqIHggKyB5ICogeSkKICBlbmQKZW5kCnB1dHMgVmVjLm5ldygzLjAsIDQuMCkubGVuZ3Ro)

`record Point(x : Int32, y : Int32)` declares a record class. Construction uses `.new`, fields are available by name, and equal field values give value equality. Its generated textual representation includes the record and field names.

`record struct Vec(...)` declares a value type with record equality. A body can add methods, as the `length` method does above.

## Class or struct?

A class instance has reference identity; a struct is copied as a value. Choose based on semantics, rather than assuming structs always improve speed. Copying a large struct can be more expensive than passing a reference, and mutable structs require care when they are retrieved from collections.

A plain `struct Name ... end` is also available when record-style positional fields and equality are not the intended API.

## Stack-only structs

`@[Stack]` on a struct emits a CLR `ref struct`. Such values have .NET's stack-only restrictions and cannot be stored freely in heap objects or used across arbitrary async lifetimes. Use this for APIs that actually need stack-only data. Normal structs are sufficient for most value types.

## Structure-of-arrays records

`@[SoA]` marks a record struct for use in `SoA(T)`. Instead of storing full records next to each other, the store keeps one array per field. This lets numeric passes work directly over columns.

```alice
@[SoA]
record struct Particle(x : Float64, v : Float64, m : Float64)
ps = SoA(Particle).new
ps.add(Particle.new(0.0, 2.0, 3.0))
ps.add(Particle.new(1.0, 4.0, 2.0))
ps.each { |p| p.x += p.v * 0.5 }
puts ps.sum { |p| p.m * p.v }
puts ps.size
```

Expected output:

```text
14
2
```

[Edit and run in the Alice playground](https://playground.alice-lang.org/#code=u.QFtTb0FdCnJlY29yZCBzdHJ1Y3QgUGFydGljbGUoeCA6IEZsb2F0NjQsIHYgOiBGbG9hdDY0LCBtIDogRmxvYXQ2NCkKcHMgPSBTb0EoUGFydGljbGUpLm5ldwpwcy5hZGQoUGFydGljbGUubmV3KDAuMCwgMi4wLCAzLjApKQpwcy5hZGQoUGFydGljbGUubmV3KDEuMCwgNC4wLCAyLjApKQpwcy5lYWNoIHsgfHB8IHAueCArPSBwLnYgKiAwLjUgfQpwdXRzIHBzLnN1bSB7IHxwfCBwLm0gKiBwLnYgfQpwdXRzIHBzLnNpemU)

The `each` block is a column update: changing `p.x` updates the store. `sum` reduces an expression over the rows. The record annotation does not turn an ordinary `Array(Particle)` into a column store; construct `SoA(Particle)` explicitly.

### Store operations

| Operation                     | Meaning                                                   |
| ----------------------------- | --------------------------------------------------------- |
| `store.add(record)`           | Append a row                                              |
| `store[i]`                    | Access a row                                              |
| `store.size`                  | Number of rows                                            |
| `store.each { \|row\| ... }`  | Operate on rows through their columns                     |
| `store.sum { \|row\| ... }`   | Sum an expression                                         |
| `store.count { \|row\| ... }` | Count matches                                             |
| `store.remove_at(i)`          | Swap-remove: the last row moves into the removed position |
| `store.clear`                 | Remove all rows                                           |

Swap-removal does not preserve order or stable indices. Keep a separate identity scheme if records are referenced elsewhere. The [performance guide](https://alice-lang.org/documentation/guides/performance.md#columnar-data) explains pass fusion and its limits.

## Continue reading

- Previous: [Classes and objects](https://alice-lang.org/documentation/language/classes.md)
- Next: [Modules and mixins](https://alice-lang.org/documentation/language/modules.md)
