+++ title = "3. Aggregate Types" weight = 3 +++
Fixed-size arrays with value semantics.
def SIZE = 5;
let ints: int[SIZE] = [1, 2, 3, 4, 5];
let zeros: [int; SIZE]; // Zero-initialized
Group multiple values together into a single compound value. Tuples support arbitrary arities (2, 3, 4, … up to 10+), nested composition, mutation of individual fields, and destructuring.
Basic usage
let pair = (1, "hello"); // 2-tuple, types inferred
let triple = (1, "hello", 3.14); // 3-tuple
let five = (1, 2, 3, 4, 5); // 5-tuple
let typed: (int, string, f64) = (1, "two", 3.0); // explicit annotation
Field access
Elements are accessed positionally with .0, .1, .2 etc., or via
raw struct field names .v0, .v1:
let t = (1, "hello", 3.14);
assert(t.0 == 1); // positional
assert(t.v1 == "hello"); // raw field name
Multiple return values
Functions can return tuples to provide multiple results:
fn stats(data: int) -> (int, int) {
return (data * 2, data + 1);
}
let r = stats(5);
assert(r.0 == 10);
assert(r.1 == 6);
Destructuring
let (sum, diff) = add_and_subtract(3, 2);
// sum = 5, diff = 1
let (a: string, b: u8) = ("hello", 42); // typed destructuring
let (x, y: i32) = (1, 2); // mixed: x inferred, y explicit
Nested tuples
let nested = ((1, 2), (3, 4));
let inner = nested.v0;
assert(inner.v0 == 1);
assert(inner.v1 == 2);
Mutation
Individual fields can be reassigned:
let t = (1, 2);
t.v0 = 99;
assert(t.v0 == 99);
String comparison
Tuples with string fields must use strcmp() for comparison,
not == (which does pointer comparison on char*):
let t = (1, "hello");
assert(strcmp(t.1, "hello") == 0);
Enum tuple payloads
Enum variants can carry tuple data:
enum Shape {
Point,
Line(int, int),
Labeled(int, string, f64),
}
match shape {
Shape::Line(x, y) => { … }
Shape::Labeled(id, name, val) => { … }
}
Data structures with optional bitfields.
struct Point {
x: int;
y: int;
}
// Struct initialization
let p = Point { x: 10, y: 20 };
// Bitfields
struct Flags {
valid: U8 : 1;
mode: U8 : 3;
}
{% alert(type="note") %}
Structs use Move Semantics by default. Fields can be accessed via . even on pointers (Auto-Dereference).
{% end %}
You can define a struct as opaque to restrict access to its fields to the defining module only, while still allowing the struct to be allocated on the stack (size is known).
// In user.zc
opaque struct User {
id: int;
name: string;
}
fn new_user(name: string) -> User {
return User{id: 1, name: name}; // OK: Inside module
}
// In main.zc
import "user.zc";
fn main() {
let u = new_user("Alice");
// let id = u.id; // Error: Cannot access private field 'id'
}
Tagged unions (Sum types) capable of holding data.
enum Shape {
Circle(float), // Holds radius
Rect(float, float), // Holds width, height
Point // No data
}
Standard C unions (unsafe access).
union Data {
i: int;
f: float;
}
Native SIMD vector types using GCC/Clang vector extensions. Annotate a struct with @vector(N) to define a vector of N elements.
import "std/simd.zc";
fn main() {
let a = f32x4{v: 1.0}; // Broadcast: {1.0, 1.0, 1.0, 1.0}
let b = f32x4{1.0, 2.0, 3.0, 4.0}; // Per-element init
let c = a + b; // Element-wise addition
let x = c[0]; // Element access (float)
}
Arithmetic (+, -, *, /) and bitwise (&, |, ^) operators work element-wise. See std/simd.zc for predefined types.
Create a new name for an existing type.
alias ID = int;
alias PointMap = Map<string, Point>
alias OpFunc = fn(int, int) -> int
You can define a type alias as opaque to create a new type that is distinct from its underlying type outside of the defining module. This provides strong encapsulation and type safety without the runtime overhead of a wrapper struct.
// In library.zc
opaque alias Handle = int;
fn make_handle(v: int) -> Handle {
return v; // Implicit conversion allowed inside module
}
// In main.zc
import "library.zc";
fn main() {
let h: Handle = make_handle(42);
// let i: int = h; // Error: Type validation failed
// let h2: Handle = 10; // Error: Type validation failed
}