Heroes / Guide / chapter 3

Records and functions

Grouping data in records and calling functions with explicit arguments.

A record groups fields with names and types. When you pass it to a function, the function receives an independent copy, as it does with a number.

Declaring one

# A position on the integer grid.
record Point
    x: i64
    y: i64
examples/gallery/01-points.hero, lines 6–9

Each field declares a name and a type on an indented line. A field can contain another record: Rect contains two point values:

# A rectangle, by two corners.
record Rect
    corner: Point
    extent: Point
examples/gallery/01-points.hero, lines 11–14

Building one: every field is named

There is one way to make a record, and it names each field:

function main()
    a = Point(x: 0, y: 0)
    b = Point(x: 3, y: 4)
    print("distance squared: ", a.dist2(b: b))
    print("midpoint x: ", a.midpoint(b: b).x)
examples/gallery/01-points.hero, lines 45–49

Point(3, 4) is invalid because the field names are missing. The compiler reports the error and suggests a fix:

$ heroes check points.hero
error[missing_label]: `Point`'s fields are always named — this one is `x`
  at points.hero:46:15
     |
  46 |     a = Point(0, 0)
     |               ^
  fix (certain): write `x: `

With field names, Point(y: 4, x: 3) and Point(x: 3, y: 4) describe the same point. Argument order does not change which value is assigned to each field.

Functions, and where the names become mandatory

function dist2(a: Point, b: Point) -> i64
    dx = a.x - b.x
    dy = a.y - b.y
    return dx * dx + dy * dy
examples/gallery/01-points.hero, lines 21–24

A function declaration specifies the type of every parameter and, after ->, the result type. Inside the body, dx = … gives a value a name and the compiler infers its type. You can therefore find the input and output types by reading the declaration.

When two parameters of a function share a type, the call must name them. This applies to dist2, which takes two Point values:

$ heroes check points.hero
error[needs_label]: two of `dist2`'s parameters are `Point`, so every one of them is named at the call site — this one is `b`
  at points.hero:48:41
     |
  48 |     print("distance squared: ", a.dist2(b))
     |                                         ^
  fix (certain): write `b: `

A type alone cannot distinguish two arguments of that type. Names make each argument’s role explicit. If the types differ, the compiler can distinguish them and labels are optional.

Calls with dot syntax

x.f(y) means f(x, y). Dot syntax is another way to write an ordinary function call. It does not define a method, a function attached to a type; the compiler determines which function to call before execution.

# Reads as a sentence, and it is one function call: `translate(r, by)`.
function translate(r: Rect, by: Point) -> Rect
    moved = Point(x: r.corner.x + by.x, y: r.corner.y + by.y)
    return Rect(corner: moved, extent: r.extent)
examples/gallery/01-points.hero, lines 33–36

unit.translate(p).area() and area(translate(unit, p)) perform the same calls. Choose the form that reads best; dot syntax lets you read the operations from left to right.

Tests live next to what they test

test "the 3-4-5 triangle"
    assert dist2(a: Point(x: 0, y: 0), b: Point(x: 3, y: 4)) == 25

test "a rectangle keeps its size when it moves"
    unit = Rect(corner: Point(x: 0, y: 0), extent: Point(x: 2, y: 3))
    assert unit.translate(Point(x: 10, y: 10)).area() == unit.area()
examples/gallery/01-points.hero, lines 38–43

A test block is declared outside functions, beside the code it checks. Ordinary builds ignore it. Here the second test checks that moving a rectangle does not change its area.

$ heroes test examples/gallery/01-points.hero
ok   "the 3-4-5 triangle"
ok   "a rectangle keeps its size when it moves"
2 tests, all passed

Function limits

Heroes has no inheritance, interfaces, overloading, meaning multiple functions with the same name, or default parameter values. You cannot define functions with a variable number of arguments. These limits reduce the rules needed to understand a call.

Next: Variants and match, where a value can be one of several shapes and forgetting one stops the build. Back to the chapter list. ← Previous: Errors

Run a program of your own: it takes four commands. Or take the whole language in one file, and every program already written in it.