← Code Compare

Structs & Records

A struct (or record) groups named fields into one composite value, laid out as a contiguous block of memory. The fields sit side by side at fixed offsets (subject to alignment padding), so a Point{x, y} is just two ints back to back - no header, no hidden pointers. The task is the same in all seven languages: declare a Point{x, y: int}, build one, translate it by (3, 4), and print the result. Watch where the struct lives - by value on the stack (the common case), or boxed on the heap when you allocate and free it explicitly.

Show: CC++HolyCZigHareOdinForth
C
#include <stdio.h>
#include <stdlib.h>

struct Point { int x, y; };   /* two ints, contiguous (+ any alignment padding) */

int main(void) {
    /* By value on the stack: no allocation, nothing to free. */
    struct Point p = { .x = 1, .y = 2 };   /* designated initializer */

    p.x += 3;                              /* dot accesses a field by name */
    p.y += 4;                              /* translate by (3,4) -> (4,6) */

    printf("Point(%d, %d)\n", p.x, p.y);   /* Point(4, 6) */

    /* Heap form: -> dereferences through a pointer; pair malloc with free. */
    struct Point *hp = malloc(sizeof *hp);
    if (!hp) return 1;
    *hp = p;                               /* copy the whole struct by value */
    printf("Point(%d, %d)\n", hp->x, hp->y);
    free(hp);                              /* one malloc, one free */
    return 0;
}

A C struct is a plain contiguous block of fields; p.x accesses by value and hp->x through a pointer. Stack structs need no cleanup, but a heap struct is sized with sizeof *hp and the single malloc is matched by a single free. Assigning one struct to another (*hp = p) copies all fields bit-for-bit.

C++
#include <iostream>
#include <memory>

struct Point {
    int x{}, y{};                 // value-initialized to 0
    // A member function bundles behavior with the data.
    void translate(int dx, int dy) { x += dx; y += dy; }
};

int main() {
    Point p{1, 2};                // aggregate init, on the stack (RAII)
    p.translate(3, 4);            // (1,2) -> (4,6)
    std::cout << "Point(" << p.x << ", " << p.y << ")\n";

    // Heap-owned by a unique_ptr: freed automatically at scope exit.
    auto hp = std::make_unique<Point>(p);   // copy-construct on the heap
    std::cout << "Point(" << hp->x << ", " << hp->y << ")\n";
    // ~unique_ptr deletes the heap Point here; no manual delete.
}

In modern C++ a struct is just a class with public defaults, so it can carry member functions like translate. Stack Points clean themselves up, and std::make_unique<Point>(p) heap-allocates a copy whose destructor runs exactly once (RAII) - no new/delete to balance by hand.

HolyC
// Top-level code runs in TempleOS -- no main() needed.
class Point {       // HolyC 'class' == C struct (no methods)
  I64 x, y;
};

Point p;            // by value on the stack/locals
p.x = 1;
p.y = 2;

p.x += 3;           // translate by (3,4) -> (4,6)
p.y += 4;
Print("Point(%d, %d)\n", p.x, p.y);   // Point(4, 6)

// Heap form: MAlloc a Point, access through -> , then Free.
Point *hp = MAlloc(sizeof(Point));    // 16 bytes from this task's heap
*hp = p;                              // copy all fields
Print("Point(%d, %d)\n", hp->x, hp->y);
Free(hp);                            // return it; Free(NULL) is a safe no-op

HolyC spells a record as class (it has no methods - it is structurally a C struct of two I64s, 16 bytes). Locals access fields with ., heap pointers with ->. MAlloc carves the struct from the per-task data heap and Free returns it; with no memory protection in ring-0 TempleOS, a stray write or double-free can corrupt the system.

Zig
const std = @import("std");

const Point = struct {
    x: i32,
    y: i32,
    // Methods live in the struct; the first param is the receiver.
    fn translate(self: *Point, dx: i32, dy: i32) void {
        self.x += dx;
        self.y += dy;
    }
};

pub fn main() !void {
    var p = Point{ .x = 1, .y = 2 };   // by value, on the stack
    p.translate(3, 4);                 // (1,2) -> (4,6)
    std.debug.print("Point({d}, {d})\n", .{ p.x, p.y });

    // Heap form: the allocator is explicit -- no hidden allocations.
    var gpa = std.heap.GeneralPurposeAllocator(.{}){};
    defer _ = gpa.deinit();            // reports leaks on exit
    const allocator = gpa.allocator();

    const hp = try allocator.create(Point);  // *Point, may fail -> try
    defer allocator.destroy(hp);             // paired free at scope exit
    hp.* = p;                                // copy the struct value
    std.debug.print("Point({d}, {d})\n", .{ hp.x, hp.y });
}

A Zig struct is a value type holding its fields inline; methods declared inside take self explicitly. Stack Points need no allocator, while a heap one uses allocator.create(Point) (returns !*Point, hence try) paired with defer allocator.destroy(hp) so the free always runs - and the GeneralPurposeAllocator flags any leak.

Hare
use fmt;

type Point = struct {
	x: int,
	y: int,
};

export fn main() void = {
	// By value, on the stack -- no alloc, nothing to free.
	let p = Point { x = 1, y = 2 };
	p.x += 3;                 // translate by (3,4) -> (4,6)
	p.y += 4;
	fmt::printfln("Point({}, {})", p.x, p.y)!;

	// Heap form: alloc returns *Point; defer the matching free.
	let hp: *Point = alloc(p);   // copy p onto the heap
	defer free(hp);              // release at scope exit
	fmt::printfln("Point({}, {})", hp.x, hp.y)!;
};

Hare declares a record with type Point = struct { ... } and accesses fields with . for both values and pointers (it auto-derefs). Stack structs are free of cleanup; alloc(p) copies the struct onto the heap and yields *Point, with defer free(hp) guaranteeing the single alloc is matched by a single free (Hare has no GC).

Odin
package main

import "core:fmt"

Point :: struct {
	x, y: int,
}

main :: proc() {
	// By value, on the stack.
	p := Point{1, 2}
	p.x += 3            // translate by (3,4) -> (4,6)
	p.y += 4
	fmt.println(p)      // Point{x = 4, y = 6}

	// Heap form: new(Point) uses the implicit context.allocator; returns ^Point.
	hp := new(Point)
	defer free(hp)      // freed via the same context allocator at scope end
	hp^ = p             // copy the struct through the pointer
	fmt.printf("Point(%d, %d)\n", hp.x, hp.y)
}

Odin defines a record with Point :: struct { ... } and accesses fields with . (pointers auto-deref, so hp.x works). new(Point) allocates through the implicit context.allocator and returns ^Point; free(hp), scheduled with defer, returns it to that same allocator - swap in an arena and you could drop the per-object free entirely.

Forth
\ Classic Forth has no struct type: a record is just a block of memory,
\ and you name the field OFFSETS yourself (here, 2 cells = x then y).
0 CELLS CONSTANT P.X      \ field x lives at offset 0
1 CELLS CONSTANT P.Y      \ field y lives at offset 1 cell
2 CELLS CONSTANT /POINT   \ total size of a Point ( '/' = size-of idiom )

CREATE p  /POINT ALLOT    \ reserve one Point's worth of cells (static)

1 p P.X + !               \ p.x = 1   ( addr = base + offset, then store )
2 p P.Y + !               \ p.y = 2

3 p P.X + +!              \ p.x += 3  ( +! adds in place )
4 p P.Y + +!              \ p.y += 4  -> (4,6)

." Point(" p P.X + @ .   ( fetch x, print )
." , "     p P.Y + @ .   ( fetch y, print )
." )" CR

Forth has no record type, so the closest idiom is a hand-laid memory block with named field offsets (P.X, P.Y) added to a base address: base offset + ! stores and base offset + @ fetches. CREATE ... ALLOT reserves the bytes statically in the dictionary (never freed); the optional ALLOCATE/FREE word set would heap-allocate /POINT bytes the same way you'd malloc a struct in C.