Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Products

Ore has one structural product surface with two shapes: positional products and named products. They are distinct types and do not unify with each other.

Positional Products

Positional products use .{ ... } with numeric field access.

t := .{ 10, 20, true }
t.0
t.1
t.2

Type annotation: .{ i32, i32, bool }.

Parentheses are only for grouping. Unit remains ().

(42)        // grouping
.{ 42 }     // 1-element positional product
()          // unit

Named Structural Products

Named products are structural values with labeled fields.

point := .{ .x = 10, .y = 20 }
point.x
point.y

Their type syntax uses the same field set with : instead of =:

.{ .x: i32, .y: i32 }

Type Annotation

alloc :: fn(n: i32) -> .{ .ptr: [*]u8, .len: usize }
  .{ .ptr = ore_alloc(n), .len = n as usize }

Field Access

Named products are accessed only by field name.

buf := alloc(100)
buf.ptr
buf.len

As Function Parameters

area :: fn(rect: .{ .w: i32, .h: i32 }) -> i32
  rect.w * rect.h

area(.{ .w = 10, .h = 5 })

Key Rules

Field names are part of identity

.{ .x: i32, .y: i32 } and .{ .a: i32, .b: i32 } are different types.

Named and positional products are distinct

.{ .x: i32, .y: i32 } does not unify with .{ i32, i32 }.

Field access matches the product family

Positional products use .0, .1, and so on. Named products use .field.

.{ ... } is structural by default

Without an explicit head, .{ ... } builds a structural product. Use a head when you want a nominal data value or an array.

Point.{ .x = 10, .y = 20 }
[3; i32].{ 1, 2, 3 }

Comparison with data

FeaturePositional ProductNamed Productdata
IdentityStructural by positionStructural by field namesNominal
Access.0, .1.name.name
Type syntax.{ A, B }.{ .x: A, .y: B }Name
Literal syntax.{ 1, 2 }.{ .x = 1, .y = 2 }Point.{ .x = 1, .y = 2 }

Use named structural products for lightweight labeled data. Use data when the type’s name should matter across the program.

Library Convention

Functions that allocate often return a named product containing the owner and its size:

read_file :: fn(path: []const u8) -> .{ .data: [*]u8, .size: i32 }
file := read_file("input.txt")
view := file.data[0..file.size as usize]