Finch/docs/en/for-technicians.md
Claude 0ceaaa27c4
Floats as written, never fused; fma and muladd; f32/f64 bits, f16 and bf16
- a * b + c is two roundings on every processor and --cpu: the IR has no fast-math flags, and clang
  compiles it with -ffp-contract=on (checked in the assembly: never fused, also with FMA hardware)
- fma(a, b, c) / v.fma(b, c): one rounding, always (llvm.fma)
- muladd(a, b, c) / v.muladd(b, c): the fastest way here (llvm.fmuladd), what v.fma was before
- f32_bits / f32_from_bits / f64_bits / f64_from_bits: bitcasts, also of vectors
- f16_to_f32 / f32_to_f16 (LLVM half), bf16_to_f32 / f32_to_bf16 (integer ops, ties to even, NaN kept)
- docs (en, pl): what is guaranteed about floats, the new built-ins

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Psc42moPLwP43Cmb9oZqSR
2026-11-01 15:25:40 +00:00

1442 lines
57 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Finch for Technicians
**For people who know their way around a computer, but little or no programming.**
This guide covers installing Finch, using the `finch` tool, the whole language, memory,
modules, C libraries, debugging and fixing common problems. It does not cover how the
compiler works inside. For that, see the [guide for engineers](for-engineers.md).
> 🇵🇱 Polska wersja: [dla-technikow.md](../pl/dla-technikow.md)
---
## Contents
1. [What Finch is](#1-what-finch-is)
2. [Installing](#2-installing)
3. [The finch command](#3-the-finch-command)
4. [How a Finch file looks](#4-how-a-finch-file-looks)
5. [Variables](#5-variables)
6. [Types](#6-types)
7. [Operators](#7-operators)
8. [Control flow](#8-control-flow)
9. [Functions](#9-functions)
10. [Arrays and maps](#10-arrays-and-maps)
11. [Text (str)](#11-text-str)
12. [Structs and methods](#12-structs-and-methods)
13. [Memory: who frees what](#13-memory-who-frees-what)
14. [Pointers](#14-pointers)
15. [Built-in functions](#15-built-in-functions)
16. [Modules](#16-modules)
17. [Using C libraries](#17-using-c-libraries)
18. [Errors](#18-errors)
19. [Editors: VS Code, Kate and others](#19-editors-vs-code-kate-and-others)
20. [Debugging](#20-debugging)
21. [Troubleshooting](#21-troubleshooting)
22. [Project layout and tests](#22-project-layout-and-tests)
23. [Enums and match](#23-enums-and-match)
24. [Generics](#24-generics)
25. [The standard library](#25-the-standard-library)
26. [Current limits](#26-current-limits)
27. [Tests: test blocks and finch test](#27-tests-test-blocks-and-finch-test)
28. [A project: finch.toml](#28-a-project-finchtoml)
29. [Threads](#29-threads)
30. [C++, Rust and Android](#30-c-rust-and-android)
31. [Vectors (SIMD)](#31-vectors-simd)
---
## 1. What Finch is
Finch is a **compiled** programming language. The `finch` tool translates your `.fch`
file into a real program (a native executable, like the ones made from C), using the
**LLVM** compiler toolkit, the same backend that powers clang, Rust and Swift.
What that gives you:
- **Speed close to C.** No interpreter, virtual machine or garbage collector. Array bounds
checks and other safety checks cost almost nothing after optimization.
- **Small, standalone programs** that you can copy and run.
- **Memory handled for you.** Lists and text are freed automatically at the end of the block
that owns them, with no garbage collector involved.
- **Direct access to C libraries.** `import "stdio.h"` and call anything in it, even functions
that take structs.
The design rules: one way to do each thing, C-like syntax without C's traps, and error
messages that tell you how to fix the problem.
---
## 2. Installing
The Finch compiler is written in Finch (see the [engineers' guide](for-engineers.md)). It runs on **Linux** (x86-64 and
ARM64), **macOS** (Apple Silicon and Intel) and **Windows** (x86-64), and builds programs for all of them. On every
system it needs **clang** (it compiles each program with it) and **libclang** from LLVM 21 (it reads C headers with it).
### Option A: a ready build
Download the archive for your system from the [releases](https://github.com/Nexoniarz/Finch/releases) and unpack it.
Keep `runtime/` and `std/` next to the `finch` file (or install it, below).
| System | Archive | Also install |
|---|---|---|
| Linux x86-64 | `finch-linux-x64.tar.gz` | clang 21 and libclang (table below) |
| Linux ARM64 (Raspberry Pi 4/5, Graviton…) | `finch-linux-arm64.tar.gz` | the same |
| macOS, Apple Silicon | `finch-macos-arm64.tar.gz` | `xcode-select --install` (clang and the system headers), `brew install llvm@21` (libclang) |
| macOS, Intel | `finch-macos-x64.tar.gz` | the same |
| Windows x64 | `finch-windows-x64.zip` (`finch.exe` with `libclang.dll`) | [llvm-mingw](https://github.com/mstorsjo/llvm-mingw/releases) (the `ucrt-x86_64` zip): unpack it and put its `bin` folder on your PATH |
On Linux:
| Distribution | Packages |
|---|---|
| Debian / Ubuntu | `clang-21 libclang-21-dev` from [apt.llvm.org](https://apt.llvm.org), `pkg-config` |
| Fedora | `clang clang-devel pkgconf` |
| Arch | `clang pkgconf` |
| Nix | `nix-shell` in the repository gives all of it |
On Windows, llvm-mingw gives Finch `clang`, the C headers and the linker in one folder; Windows programs built with it
need nothing else to run. Keep `libclang.dll` next to `finch.exe`.
### Option B: build it yourself
```sh
git clone https://github.com/Nexoniarz/Finch.git
cd Finch
nix-shell # Linux: clang, libclang, pkg-config (or install them with your distribution)
bootstrap/build.sh # build/finch
./build/finch version
```
`bootstrap/build.sh` compiles the seed (`bootstrap/<system>.ll`, the compiler's own LLVM IR) into a first compiler, builds
`src/` with it, builds `src/` again with the result and checks the two are identical. If libclang isn't where the linker
looks, tell it: `FINCH_LIBCLANG="-L/usr/lib/llvm-21/lib -lclang-21" bootstrap/build.sh`.
On a Mac, with Homebrew's LLVM:
```sh
brew install llvm@21
export FINCH_CLANG="$(brew --prefix llvm@21)/bin/clang"
export FINCH_LIBCLANG="-L$(brew --prefix llvm@21)/lib -lclang"
export FINCH_LLVM_INCLUDE="$(brew --prefix llvm@21)/include"
bootstrap/build.sh
```
On Windows, in Git Bash, with llvm-mingw on the PATH and the official LLVM for Windows (for `libclang.dll` and its headers)
unpacked to `C:/llvm`:
```sh
export FINCH_LIBCLANG="C:/llvm/bin/libclang.dll"
export FINCH_LLVM_INCLUDE="C:/llvm/include"
export FINCH_LDFLAGS="-LC:/llvm/bin"
bootstrap/build.sh # build/finch.exe, with libclang.dll copied next to it
```
### Building for another system
`--target` builds a program for another system. Finch needs that system's C compiler to link:
| `--target` | Builds | C compiler used | `finch run` uses |
|---|---|---|---|
| `windows` | a Windows `.exe` | `x86_64-w64-mingw32-gcc` (MinGW-w64) | Wine |
| `arm64` | an ARM64 Linux program | `aarch64-linux-gnu-gcc` | `qemu-aarch64` |
| `linux` | an x86-64 Linux program | `clang --target=…` | |
| `macos` | a macOS program (on a Mac: for the other processor) | `clang --target=…` | |
| any LLVM triple | e.g. `aarch64-unknown-linux-gnu` | `clang --target=<triple>` | |
```sh
finch build game.fch --target windows # game.exe
finch run game.fch --target arm64 # builds for ARM64 and runs it with QEMU
FINCH_CC=aarch64-unknown-linux-gnu-gcc finch build tool.fch --target arm64 # another compiler name
```
On Nix the compilers are `pkgsCross.mingwW64.buildPackages.gcc` and
`pkgsCross.aarch64-multiplatform.buildPackages.gcc`.
### Putting `finch` on your PATH (optional)
```sh
sudo bootstrap/install.sh # /usr/local/bin/finch, and /usr/local/share/finch/{runtime,std}
bootstrap/install.sh ~/.local # or into your own folder
finch version
```
At run time Finch needs `clang` (it compiles each program's IR and links it). The small runtime library is built once and
kept in `~/.cache/finch`. `FINCH_CC` names a different compiler to link with; `FINCH_PATH` adds folders to look for modules in.
---
## 3. The finch command
```
finch run <file.fch> [args...] compile and run right away
finch build <file.fch> [-o name] compile into a program (default name: the file's name)
finch test <file.fch> run its test "..." { } blocks (section 27)
finch fmt [--check] [files/dirs] tidy the indentation of .fch files (all under here by default)
finch ir <file.fch> print the LLVM IR (for the curious)
finch lsp the language server, for editors
finch version show the version
options:
-l <lib> link a C library, same as link "lib" in the file
-I <dir> look for C headers there too
--target <name> build for another system: windows, linux, arm64, macos, android, or an LLVM triple
--cpu <name> use every instruction of a processor: native (this one), x86-64-v3 (AVX2),
x86-64-v4 (AVX-512), apple-m1, ... (section 31)
-g add debug info (for gdb and lldb)
-O0 skip optimizations
--trace runtime errors show the chain of calls (on anyway for run, test, -g and -O0)
--no-trace they don't (a plain finch build doesn't: keeping the chain makes calls slower)
```
In a folder with a `finch.toml` (section 28), `finch run`, `finch build` and `finch test` need no file name.
The compiled program and the C files it builds (`link "x.c"`) are kept in a cache (`~/.cache/finch`, or
`%LOCALAPPDATA%` on Windows). The second build of the same code, or of code whose C files did not change, skips that work.
`finch fmt` changes only the layout: four spaces per `{ }` level, no spaces at line ends, at most one empty line in a row.
Lines that continue an expression (inside `( )` or after a `+`) and `"""` text keep their shape. `finch fmt --check`
changes nothing and fails if a file would change (for CI). Editors with the language server format on request.
Examples:
```sh
finch run hello.fch # prints the output
finch run tool.fch input.txt -v # arguments after the file go to the program
finch build game.fch -o mygame # creates ./mygame
finch build game.fch -g -O0 # a build for the debugger
```
`finch run` passes on your program's exit code. If the program crashes, it says how
(for example `the program crashed: Segmentation fault`).
---
## 4. How a Finch file looks
```c
// comments start with two slashes
/* or span
several lines */
import "math.h" // C headers (optional), at the top
import shapes // Finch modules (optional): shapes.fch next to this file
link "m" // C libraries or C files to link (optional)
struct Point { // your own types
int x
int y
}
fn main() { // the program starts here
print("Hello")
}
```
Rules worth knowing:
- **One statement per line.** There are no semicolons. To split a long expression, break the line
**after** an operator (`a +⏎ b`) or anywhere inside brackets `( )` or `[ ]`.
- **Curly braces are always required** after `if`, `else`, `for`, `while`, and the opening `{`
goes on the same line.
- **No brackets around conditions:** `if x > 5 {`, not `if (x > 5) {`.
- Functions and structs can be in any order in the file.
- `main` is `fn main()`, `fn main() -> int` (returns the exit code) or `fn main([]str args)`
(gets the command-line arguments; `args[0]` is the program's own path).
---
## 5. Variables
```c
x := 5 // new variable, type inferred (int)
int y = 10 // new variable, type written explicitly
int z // no value: starts at zero ("" for str, [] for arrays, null for pointers)
Point p // a struct with no value: zero, or the struct's default values
x = 7 // assign
x += 1 // also -= *= /= %=
```
- A name can be declared **once** in a function. Finch does not allow reusing a name in an
inner block (no "shadowing"), because that is a common source of confusion.
- Variables exist from their declaration to the end of the `{ }` block they are in.
- Names: ASCII letters, digits and `_`, not starting with a digit. Non-ASCII letters are allowed only inside strings.
Type names (`int`, `u8`, …), built-in function names and keywords are reserved.
**Keywords:** `fn return if else while for in break continue true false null import link struct enum match defer or try const export`
### Constants
```c
const WIDTH = 640
const HEIGHT = WIDTH / 2 // from other constants
const f32 GRAVITY = 9.8 // with a type
const TITLE = "My game"
```
A constant is written at the top level of a file, next to the functions, and can't be changed. It is made of
numbers, text, `true`/`false`, chars, other constants and operators; a module's constant is `shapes.SIDES`.
---
## 6. Types
### Everyday types
| Type | Holds | Example |
|---------|-------|---------|
| `int` | whole number, 64-bit (same as `i64`) | `42`, `-7`, `0xFF`, `1_000_000` |
| `float` | decimal number, 64-bit (same as `f64`) | `3.14` |
| `bool` | `true` or `false` | |
| `char` | one byte-sized character | `'A'`, `'\n'` |
| `str` | text | `"hello\tworld"` |
| `[]T` | a list (array) of `T` | `[1, 2, 3]` |
| `map[K]V` | values of type `V` found by a key of type `K` | `["a": 1, "b": 2]` |
| your structs | named groups of values | `Point(1, 2)` |
String and char escapes: `\n` new line, `\t` tab, `\r`, `\0`, `\\`, `\"`, `\'`.
### Sized types (for files, hardware, C libraries)
| Signed | Range | Unsigned | Range |
|---|---|---|---|
| `i8` | −128 … 127 | `u8` | 0 … 255 |
| `i16` | −32 768 … 32 767 | `u16` | 0 … 65 535 |
| `i32` | about ±2.1 billion | `u32` | 0 … about 4.29 billion |
| `i64` | about ±9.2 × 10¹⁸ | `u64` | 0 … about 1.8 × 10¹⁹ |
`f32` is a 32-bit decimal number (about 7 significant digits), `f64` a 64-bit one (about 15–16).
### Conversions
Finch converts automatically **only when nothing can be lost**:
| From | To | Automatic? |
|---|---|---|
| a smaller whole number | a bigger one of the same signedness (`i8`→`i32`, `u8`→`u64`) | ✅ |
| unsigned | a strictly bigger signed (`u8`→`i16`, `u32`→`i64`) | ✅ |
| any whole number | `float` / `f32` | ✅ |
| `f32` | `f64` | ✅ |
| `null` | any pointer | ✅ |
| any pointer | `ptr`, and `ptr` to any pointer | ✅ |
| anything else | | ❌ write it: `type(value)` |
**Number literals adapt.** A number written in the code fits into any type that can hold it:
`u8 b = 200` works, `u8 b = 300` is an error. The same applies to constants imported from C headers.
**Explicit conversions** use the type name like a function:
```c
int(3.99) // 3 (cuts off the fraction)
float(7) / 2 // 3.5
u8(300) // 44 (keeps the lowest 8 bits)
int('A') // 65
char(66) // 'B'
bool(0) // false (any non-zero number is true)
str(42) // "42" (also floats, bools, chars; str([]u8) turns bytes into text)
int("42") // 42 (stops the program if the text isn't a number)
float("2.5") // 2.5
ptr(p) // a typed pointer as an untyped one
ptr(16) // a number as a pointer (C APIs that pass offsets as pointers, like OpenGL)
int(p) // a pointer as a number (its address)
str(p) // a C char pointer as text
```
**Fixed-size numbers wrap around**, like real hardware: a `u8` holding 255 plus 1 becomes 0.
Calculations happen in the type of the values involved, so `u8(250) + 10` is `4`, not `260`.
### Decimal numbers: what is guaranteed
`f32` and `f64` are IEEE 754 numbers, and Finch computes them **exactly as written**, the same on every processor and
with every `--cpu`:
- Each operation rounds once, in the order written. `a * b - c * d` is two multiplies and a subtract, never folded into a
fused multiply-add (C compilers often do that by default, which changes the last digits).
- Nothing is reordered or simplified: `(a + b) + c` is not turned into `a + (b + c)`, and `x * 0.0` is not assumed to be 0
(it is NaN when `x` is). The one exception is `v.sum()` on a vector, which adds the lanes in the fastest order.
When speed matters more than the last bit, ask for it by name:
| | |
|---|---|
| `fma(a, b, c)` | `a * b + c` with a single rounding, always. One instruction where the processor has FMA (with `--cpu`, on ARM64); a slower library call elsewhere. |
| `muladd(a, b, c)` | `a * b + c` the fastest way on this processor: fused where it can be, a multiply and an add where not. |
Both work on numbers and on vectors (`v.fma(b, c)`, `v.muladd(b, c)`).
**Numbers as bits.** File formats and model weights store numbers as raw bits. These look at the bits without
converting the value (all of them also work lane by lane on vectors):
| | |
|---|---|
| `f32_bits(x)` / `f32_from_bits(u)` | `f32` ↔ the `u32` with the same bits (`f32_bits(1.0)` is `0x3f800000`) |
| `f64_bits(x)` / `f64_from_bits(u)` | `f64` ↔ `u64` |
| `f16_to_f32(h)` / `f32_to_f16(x)` | half precision, kept in a `u16`, ↔ `f32` (rounded to the nearest) |
| `bf16_to_f32(b)` / `f32_to_bf16(x)` | bfloat16, kept in a `u16`, ↔ `f32` (rounded to the nearest, ties to even) |
```c
[]u16 weights = read_weights() // f16 numbers from a file
v := f16_to_f32(u16x8(weights, 0)) // 8 of them as f32, at once
```
---
## 7. Operators
### By priority (higher binds tighter)
| Priority | Operators | Meaning |
|---|---|---|
| 5 | `*` `/` `%` `<<` `>>` `&` | multiply, divide, remainder, shift left/right, bitwise AND |
| 4 | `+` `-` `\|` `^` | add (and join text), subtract, bitwise OR, bitwise XOR |
| 3 | `==` `!=` `<` `<=` `>` `>=` | comparisons |
| 2 | `&&` | logical AND |
| 1 | `\|\|` | logical OR |
Unary (in front of a value): `-x` negate, `!x` logical NOT, `~x` flip all bits.
> **Different from C, on purpose:** bit operators bind tighter than comparisons, so
> `flags & 4 != 0` means `(flags & 4) != 0`.
Notes:
- `/` on whole numbers rounds toward zero: `7 / 2` is `3`. `%` takes the sign of the left side.
- Dividing by zero **stops the program** with `runtime error: division by zero` and the line number.
- `&&` and `||` only evaluate the right side if needed.
- Text: `+` joins, `==` `!=` compare contents, `<` `>` compare alphabetically (byte by byte).
- Pointers can be compared with `==` and `!=`, including against `null`. A `str` is never `null`.
- Arrays and structs can't be compared with `==`; compare their parts.
---
## 8. Control flow
`if` can also give a value; each branch holds one value, and the `else` is needed:
```c
label := if score > 100 { "high" } else if score > 10 { "mid" } else { "low" }
f32 speed = if running { 2.5 } else { 1.0 }
```
```c
if x > 10 {
...
} else if x > 0 {
...
} else {
...
}
while count > 0 {
count -= 1
}
for i in 0..10 { // i = 0, 1, …, 9 (the end is not included)
if i == 3 { continue }
if i == 8 { break }
}
for name in names { // every element of an array (or every char of a str)
print(name)
}
for i, name in names { // with its index: 0, 1, 2, ...
print(i, name)
}
for key, value in ages { // every entry of a map, in the order the keys were added
print(key, value) // (for key in ages: just the keys)
}
```
- A condition must be a `bool`. `if x {` with a number is an error; write `if x != 0 {`.
- In `for i in a..b`, both ends are evaluated **once**. `i` is an `int` and cannot be changed in the loop.
- In `for x in list`, `x` cannot be changed either. To change elements, loop over the indexes:
`for i in 0..list.len { list[i] = ... }`. Adding to the list inside the loop is fine.
- `while true { ... }` loops until a `break` or `return`.
- Code after `return`, `break` or `continue` in the same block is an error, because it can never run.
### defer
`defer` runs a statement when the current block ends, however it ends: normally, by `return`,
`break` or `continue`. Several defers run in reverse order. Handy for cleanup:
```c
f := fopen("data.txt", "r")
defer fclose(f)
// ... use f; it is closed when the block ends, on every path
```
A deferred statement can't contain `return`, `break` or `continue`.
---
## 9. Functions
```c
fn name(type1 param1, type2 param2) -> return_type {
...
return value
}
fn no_result(str message) { // no "->": returns nothing
print(message)
}
```
- Arguments behave like copies: changing a parameter never changes the caller's variable.
(Finch only really copies when needed: a function that doesn't change an array gets it for free.)
To change the caller's variable, pass a pointer: see [Pointers](#14-pointers).
- A function with `-> type` must return a value on every path. Finch checks this.
- A function cannot be defined inside another function.
- A function that can fail is written `-> int!` (or `-> !` with no result); see
[Errors as values](#errors-as-values).
- Long calls can be split over several lines inside the brackets; a comma after the last argument is fine.
---
## 10. Arrays and maps
```c
nums := [5, 3, 8] // an array of int
[]str names // an empty array of str
grid := [[1, 2], [3, 4]] // arrays of arrays
[]f32 xs = [1, 2.5] // the type decides what the numbers become
```
| Operation | Meaning |
|---|---|
| `a.len` | number of elements |
| `a[i]` | element `i` (from 0); out of range stops the program with a clear message |
| `a[i] = x` | change an element |
| `a.push(x)` | add at the end |
| `a.pop()` | remove the last one and give it back |
| `a.insert(i, x)` | put `x` at position `i`, moving the rest |
| `a.remove(i)` | remove position `i` and give it back |
| `a.clear()` | remove everything |
| `a.resize(n)` | make it `n` long (new elements are zero) |
| `a.contains(x)`, `a.find(x)` | is `x` there? its index, or -1 (numbers, chars, bools, str) |
| `a.sort()`, `a.reverse()` | in place (numbers, chars, str) |
| `a.slice(s, e)` | a new array with elements `s` … `e-1` |
| `a.join(sep)` | for `[]str`: one text with `sep` between the parts |
| `a.ptr` | the address of the first element (for C) |
| `print(a)` | prints `[1, 2, 3]` |
**Assigning copies:** after `b := a`, changing `b` does not change `a`.
### Maps
A `map[K]V` finds values by a key. Keys can be whole numbers, `char`, `bool` or `str`; values can be
anything (arrays, structs, other maps). Iterating goes in the order the keys were first added.
```c
ages := ["anna": 31, "bob": 25] // a map literal: map[str]int
map[str][]str groups // an empty map
map[int]str names = [:] // [:] is an empty map where the type is known
```
| Operation | Meaning |
|---|---|
| `m[k]` | the value for `k`; a missing key stops the program with `the key "k" is not in the map` |
| `m[k] = v` | add or replace |
| `m[k] += 1`, `m[k].push(x)`, `m[k].field = …` | changing a missing key adds it first, with the value type's default (0, `""`, `[]`, …) |
| `m.len` | the number of keys |
| `m.has(k)` | is `k` there? |
| `m.get(k, default)` | the value, or `default` (only computed when needed) |
| `m.remove(k)` | removes `k`; `true` if it was there |
| `m.clear()` | removes everything |
| `m.keys()`, `m.values()` | new arrays, in insertion order |
| `print(m)`, `str(m)` | `{"anna": 31, "bob": 25}` |
```c
map[str]int counts
for word in text.split(" ") {
counts[word] += 1 // counting: a new word starts at 0
}
for word, n in counts {
print(word, n)
}
```
Lookups are hash-based (average constant time). Like arrays, maps own their keys and values,
copy on assignment, and are freed at the end of their block. Don't add or remove keys of a map
inside a `for` over that same map; collect them in an array and change the map after the loop.
---
## 11. Text (str)
```c
s := "Hello"
s += ", world" // join
print(s.len, s[0]) // 12 H (.len counts bytes; ą, ż, … take 2)
s[0] = 'J' // change a character
```
| Method | Gives back |
|---|---|
| `s.sub(a, b)` | the part from `a` to `b-1` |
| `s.find(t)` | where `t` starts, or -1 |
| `s.contains(t)`, `s.starts_with(t)`, `s.ends_with(t)` | `bool` |
| `s.split(sep)` | `[]str`; `split("")` gives single characters |
| `s.trim()` | without spaces/newlines at both ends |
| `s.upper()`, `s.lower()` | A–Z / a–z changed (ASCII letters) |
| `s.replace(a, b)` | every `a` replaced by `b` |
| `s.repeat(n)` | `s` n times |
| `s.bytes()` | `[]u8` with the bytes |
| `s.ptr` | a `ptr[char]` for C (valid while `s` lives) |
Values go into text with `\(...)`: any code, and `, 2` after a number for two digits after the point.
A `\` before the `(` you want to keep: `"\\(not code)"`.
```c
print("hp \(hp) of \(max_hp), speed \(v, 2), name \(p.name.upper())")
```
`str` keeps its length, so `.len` is instant, and it is always NUL-terminated, so passing it
to C as `char*` works. The other way round, a C function that gives back a `char *` gives you a `str`
(a copy is made when you keep it), and `str(p)` reads a `ptr[char]` you got some other way.
Text over several lines goes between `"""`. The line break after the opening quotes is dropped, and so is the
indentation of the closing quotes, from every line, so the text can be indented with your code:
```c
shader := """
#version 330
void main() { gl_Position = vec4(0.0); }
"""
```
Numbers as text: `str(x)` is the shortest text that reads back as the same number (`0.1`, `0.30000000000000004`,
`1e+20`); `str(x, 2)` always has two digits after the point (`3.14`).
---
## 12. Structs and methods
```c
struct Player {
str name
int lives = 3 // a default value
[]int scores
Point pos // structs inside structs
}
p := Player(name: "Ola") // by name: missing fields get their default (or zero)
q := Point(1, 2) // in order: then every field must be given
p.lives -= 1
p.scores.push(10)
p.pos.x = 5
print(p) // Player(name: "Ola", lives: 2, scores: [10], pos: Point(x: 5, y: 0))
```
- Assigning or passing a struct copies it, together with its arrays and text.
- A struct can't contain itself directly; use `ptr[Node]` or `[]Node` for that field (trees, lists).
### Methods
A method is a function that belongs to a struct: `fn Struct.name(...)`. Inside, `self` is the struct
it was called on, and it is the caller's value itself, not a copy: changes to `self` stay.
```c
fn Player.heal(int amount) {
self.lives += amount
}
fn Player.is_alive() -> bool {
return self.lives > 0
}
p := Player(name: "Ola")
p.heal(2) // p.lives is now 5
if p.is_alive() { ... }
team[0].heal(1) // on an array element, a field, m[key], through a ptr[Player] ...
```
- A method goes in the same file (module) as its struct, and is available wherever the struct is.
- Methods can also be added to **structs from C headers**: `fn Vector2.length() -> f32 { ... }`.
- A method can't have the name of a field.
- Calling a method that changes `self` on a loop variable (`for p in team { p.heal(1) }`) is an error,
because the loop variable is read-only; use `for i in 0..team.len { team[i].heal(1) }`.
- Like functions, methods can fail (`-> !`); see [Errors as values](#errors-as-values).
---
## 13. Memory: who frees what
You never free arrays, text or structs. Finch does it for you, at a predictable moment:
**when the block that owns the value ends** (`}`), or earlier on `return`, `break`, `continue`.
- A variable **owns** its value. Assigning copies it, so two variables never share one list.
- A value produced by a function call or an expression is **moved** where it's stored: no copy.
- `return list` moves the variable out of the function: no copy.
- Function parameters are **lent**: the function gets the caller's value without a copy. If the
function changes the parameter, Finch makes it a private copy first. You never notice except in speed.
For memory you manage yourself (linked lists, trees, sharing between structures):
```c
node := new(Node(value: 1)) // put a value on the heap, get a ptr[Node]
defer free(node) // free(...) gives it back (and frees its arrays and text)
```
Memory from C (`malloc`, library objects) is freed with C's functions, as in C.
---
## 14. Pointers
A pointer holds the **address** of a value. Finch uses words instead of C's `*` and `&`:
| C | Finch | Meaning |
|---|---|---|
| `int *p` | `ptr[int] p` | p points to an int |
| `&x` | `addr(x)` | the address of x (also `addr(a[i])`, `addr(s.field)`) |
| `*p` | `p.value` | the value p points to |
| `p->field` | `p.field` | a field of the struct p points to |
| `p[i]` | `p[i]` | C-style indexing (no bounds check) |
| `NULL` | `null` | points nowhere |
| `void *` | `ptr` | points to something of unknown type |
```c
fn double(ptr[int] p) {
p.value = p.value * 2
}
x := 21
double(addr(x)) // x is 42
```
If a struct has its own field called `value`, then `p.value` on a pointer to it means that field.
Using `.value`, `.field` or `[i]` on a `null` pointer **stops the program** with a clear message.
⚠️ A pointer to a variable must not be used after the variable's block has ended. Finch does not check this.
---
## 15. Built-in functions
| Function | What it does |
|---|---|
| `print(a, b, ...)` | Prints all values separated by spaces, then a new line. Works with every type, including arrays and structs. |
| `input()`, `input("question")` | Reads one line typed by the user (without the newline). Empty text at end of input. |
| `read_file(path)` | The whole file as `str`. Stops the program with a clear message if it can't be read, unless handled: `read_file(p) or ...`. |
| `write_file(path, text)` | Writes (replaces) the file; gives back `true` if it worked. With `or` / `try`, the failure carries the reason. |
| `file_exists(path)` | `true` / `false` |
| `delete_file(path)` | Deletes the file; `true` if it worked (or the reason, with `or` / `try`). |
| `error(message)` | The failure a fallible function returns: `return error("...")`. |
| `shell(command)` | Runs a shell command, gives back its exit code. |
| `exit(code)` | Ends the program right away. |
| `addr(x)` | A pointer to `x`; `addr(function)` gives C a function pointer (callback). |
| `new(value)`, `free(p)` | Manual heap memory (see [Memory](#13-memory-who-frees-what)). |
| `fma(a, b, c)`, `muladd(a, b, c)` | `a * b + c` with one rounding / the fastest way (see [Decimal numbers](#decimal-numbers-what-is-guaranteed)). |
| `f32_bits(x)`, `f32_from_bits(u)`, `f16_to_f32(h)`, … | A number's bits, and half-precision numbers (see [Decimal numbers](#decimal-numbers-what-is-guaranteed)). |
| `int(x)`, `str(x)`, `u8(x)`, … | Conversions (see [Conversions](#conversions)). `str(x)` works on every type and gives the same text `print` shows. `int(text) or 0` handles text that isn't a number. |
---
## 16. Modules
Split a program into files. `import shapes` loads `shapes.fch` from the same folder (or from a
folder listed in the `FINCH_PATH` environment variable, separated by `:`).
```c
// shapes.fch
struct Box {
float w
float h
}
fn area(Box b) -> float {
return b.w * b.h
}
```
```c
// main.fch
import shapes
fn main() {
b := shapes.Box(2, 3)
print(shapes.area(b))
shapes.Box other // a module's type in a declaration
}
```
- Everything in a module is used with the module's name in front: `shapes.area`, `shapes.Box`.
- A module doesn't have `main`. Modules can import other modules.
- Modules in folders: `import ui/button` loads `ui/button.fch` and is used as `button.draw()`.
- Another name: `import ui/button as btn` → `btn.draw()`. Two imports can't use the same name.
---
## 17. Using C libraries
### Step 1: import the header
```c
import "stdio.h"
import "GLFW/glfw3.h" // a path inside the system include folders
import "mylib.h" // a header next to your .fch file
```
Finch reads the header with **libclang** (clang's C parser) and makes these usable:
- **functions**, including ones with a variable number of arguments, like `printf`,
- **structs**, by value and by pointer (`Vector2`, `SDL_Rect`, `CXCursor`, …), with their fields,
- **`enum` values** and **simple `#define` constants** (`M_PI`, `GL_COLOR_BUFFER_BIT`),
- **global variables**, like `stdout` and `stderr`.
### Step 2: link the library
```c
link "glfw" // uses libglfw.so (pkg-config is asked first, if installed)
link "helpers.c" // compile and link your own C file (next to the .fch file)
link "prebuilt.a" // or an object file / static library
```
If you forget the `link` line, Finch tells you exactly what is missing:
```
error: glfwInit, glfwCreateWindow, glfwMakeContextCurrent and 5 more come from "GLFW/glfw3.h", but its library isn't linked
...
link "glfw"
```
### How C types appear in Finch
| C | Finch |
|---|---|
| `char`, `signed char`, `unsigned char` | `char`, `i8`, `u8` |
| `short`, `int`, `long`, `long long` | `i16`, `i32`, `i64`, `i64` (by actual size) |
| `unsigned …` | `u16`, `u32`, `u64` |
| `float`, `double` | `f32`, `f64` |
| `_Bool` / `bool` | `bool` |
| `enum` | the matching signed integer |
| `char *` as a **parameter or result** | `str` |
| `char *` inside a struct or behind a pointer | `ptr[char]` (use `str(p)` to read it) |
| `struct X` by value | `X`, a struct with the same fields |
| `struct X *`, `int *`, … | `ptr[X]`, `ptr[i32]`, … |
| `int arr[16]` inside a struct | `[16]i32`: index it and use `.len` |
| `void *`, function pointers, incomplete structs | `ptr` |
### Example: structs and callbacks
```c
import "raylib.h"
link "raylib"
fn main() {
InitWindow(800, 450, "Finch")
while !WindowShouldClose() {
BeginDrawing()
ClearBackground(Color(r: 30, g: 30, b: 40, a: 255))
DrawCircleV(Vector2(400, 225), 50, Color(255, 136, 0, 255))
EndDrawing()
}
CloseWindow()
}
```
To give C a function to call back (like `qsort`'s comparator), use `addr(myFunction)`.
Its parameters must be C types (numbers, pointers, C structs).
### Graphics: OpenGL and Vulkan
Both work directly; `examples/opengl.fch` and `examples/vulkan.fch` are complete programs.
- **OpenGL 3+** on Linux: the modern functions (`glCreateShader`, `glGenVertexArrays`, …) are only
declared when `GL_GLEXT_PROTOTYPES` is defined. Finch can't define C macros, so put the includes in a
tiny header next to your program and import that:
```c
// opengl.h
#define GL_GLEXT_PROTOTYPES
#include <GL/gl.h>
#include <GL/glext.h>
#include <GLFW/glfw3.h>
```
Then `import "opengl.h"`, `link "glfw"`, `link "GL"`. Out-parameters use `addr(...)`
(`glGenBuffers(1, addr(vbo))`), buffer data uses `.ptr` (`vertices.ptr`), and byte offsets use
`ptr(...)` (`glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, stride, ptr(8))`).
- **Vulkan**: `import "vulkan/vulkan.h"` and `link "vulkan"`. Vulkan's create-info structs are built with
named fields (missing ones are zero), handles are `ptr`, and lists come from the usual "call twice"
pattern: once for the count, once with `list.ptr` after `list.resize(count)`. Function-like macros such as
`VK_MAKE_API_VERSION` aren't available; compute the number instead (`1 << 22` is Vulkan 1.0).
### What does not work yet
- C **unions** and structs with **bit fields** passed to or returned from a C function **by value** (pointers to them
work, and so do unions as variables: `SDL_Event e`, `e.type`, `e.key.key`, `SDL_PollEvent(addr(e))`).
- **Function-like macros** (`#define MAX(a, b) ...`). Macros that are a number, a text, an expression of them
(`(1u << 3)`, `((Uint64)1)`, `(A | B)`) or a struct of numbers (raylib's `RAYWHITE`, `(Color){ 245, 245, 245, 255 }`) work.
- `long double`, 128-bit integers.
---
## 18. Errors
### Errors as values
Some failures are normal: the user types `abc`, a file is missing. Finch handles them without
exceptions and without hidden control flow. A function that can fail says so with `!` after its result
type, and fails with `return error("message")`:
```c
fn parse_port(str text) -> int! {
n := int(text) or { return error("'" + text + "' is not a number") }
if n < 1 || n > 65535 {
return error("port " + str(n) + " is out of range")
}
return n
}
fn save(str path, str text) -> ! { // can fail, gives back nothing
try write_file(path, text)
}
```
The caller **must** handle the failure, in one of three ways (forgetting is a compile error):
| Code | On failure |
|---|---|
| `port := parse_port(s) or 8080` | use this value instead (it's only computed on failure; it can be another call with `or`) |
| `port := parse_port(s) or { print(err); return }` | run the block; `err` is the message (a `str`). The block must leave (`return`, `break`, `continue`, `exit`) unless the result isn't used. |
| `port := try parse_port(s)` | fail the current function with the same error (it must be fallible too) |
```c
fn load(str path) -> Config! {
text := try read_file(path)
port := try parse_port(text.trim())
return Config(port: port)
}
fn main() -> ! { // main can fail too: the error is printed, and the exit code is 1
config := try load("app.conf")
...
}
```
Built-ins that can fail work the same way: `int(text)`, `float(text)`, `read_file`, `write_file`,
`delete_file`. Without `or` / `try` they behave as before (`int("x")` stops the program,
`write_file` gives back a `bool`).
Under the hood a fallible function returns its value plus a flag and the message: no allocation on
success, no unwinding, and the cost of a failure is building its message.
### Compile errors
```
game.fch:12:9: error: 'score' must be int, but this is str
12 | score = "high"
| ^
```
Format: `file:line:column: error: explanation`, then the line with a `^` under the spot.
### Runtime errors
Instead of undefined behavior (as in C), Finch stops with a message and exit code 1:
| Message | Cause |
|---|---|
| `runtime error: division by zero` | `/` or `%` by a variable that was 0 |
| `runtime error: division overflows …` | the smallest signed number divided by −1 |
| `runtime error: index 5 is out of range (the length is 3)` | `a[5]` on a shorter array or str |
| `runtime error: pop() on an empty array` | |
| `runtime error: used .value on a null pointer` | also `.field` and `[i]` through `null` |
| `runtime error: can't turn "x" into int` | `int(...)` / `float(...)` on text that isn't a number |
| `runtime error: can't read the file "…"` | `read_file` on a missing or unreadable file (handle it with `or`) |
| `runtime error: the key "x" is not in the map …` | `m["x"]` when the map has no such key (use `.has` or `.get`) |
| `runtime error: called .f() on a null pointer` | a method called through a `null` `ptr[T]` |
| `runtime error: assert failed: …` | `assert(cond, "…")` with a false `cond` |
Under the message comes the chain of calls that led there, the innermost first:
```
game.fch:6: runtime error: index 3 is out of range (the length is 2)
in Enemy.next (game.fch:6)
in step (game.fch:10)
in main (game.fch:15)
```
`finch run`, `finch test`, `-g` and `-O0` builds keep it; a plain `finch build` leaves it out because keeping it costs
time in every call (about 3x in a program that is nothing but tiny calls, much less in a real one). `--trace` puts it in.
---
## 19. Editors: VS Code, Kate and others
Every editor feature comes from the same language server, `finch lsp`, which uses the real compiler:
errors as you type, types and docs on hover, go to definition, completion (fields, methods, map and array
methods after `.`, module members, names from C headers), the file's outline, and parameter hints.
### Kate (and KWrite, KDevelop)
```sh
editors/kate/install.sh
```
It installs the highlighting (`~/.local/share/org.kde.syntax-highlighting/syntax/finch.xml`) and adds `finch`
to Kate's LSP client settings, keeping your other servers. Restart Kate and enable the **LSP Client** plugin.
To run programs, add `finch run %f` as a target in the **Build & Run** plugin; Finch's
`file:line:column: error:` messages are then clickable. Details: `editors/kate/README.md`.
### VS Code
The **Finch** extension (`editors/vscode`, also attached to each release as a `.vsix` file) gives you:
highlighting, errors as you type, completion (also fields and methods after `.`), types on hover,
go to definition (F12), an outline of the file, parameter hints, snippets, and a ▶ **Run** button
(`Ctrl+F5`). Errors from **Finch: Build This File** appear in the Problems panel.
Install: Extensions → `…` → *Install from VSIX…* → pick `finch-lang-*.vsix`. The extension runs `finch`
from your PATH; if it is somewhere else, set **Finch: Path** in the settings.
### Others
Neovim, Helix, Zed, Emacs, Sublime and any other editor that speaks the Language Server Protocol:
run `finch lsp` for files ending in `.fch` (stdin/stdout, no options).
## 20. Debugging
```sh
finch build game.fch -g -O0 -o game
gdb ./game
(gdb) break game.fch:12
(gdb) run
(gdb) bt # where are we, through which functions
(gdb) info locals # all variables
(gdb) print player.lives
```
`-g` adds debug info (lines, functions, variables, struct fields). `-O0` keeps every variable
visible; without it the optimizer may remove some. On Windows the debug info is DWARF as well: use the `lldb` that comes with llvm-mingw, or gdb.
---
## 21. Troubleshooting
| Problem | Solution |
|---|---|
| `can't find the C header 'x.h'` | The library's development files are missing. Install them (Debian: `libx-dev`; Nix: add to `shell.nix`). |
| `… come from "x.h", but its library isn't linked` | Add `link "name"` at the top of the file. |
| `the library 'x' wasn't found` | Not installed, or a wrong name. On Nix, add it to `shell.nix` and run inside `nix-shell`. |
| `the C function 'f' can't be used from Finch yet` | It uses a union, bit fields or another unsupported type by value. Look for a variant that takes pointers. |
| `--cpu native`: the program stops with `Illegal instruction` on another computer | It was built for the processor that built it. Build without `--cpu`, or with an older one (`x86-64-v2`). |
| `can't find the module 'x'` | Put `x.fch` next to the importing file, or set `FINCH_PATH`. |
| `couldn't build the Finch runtime` | No working C compiler. Install gcc or clang, or set `CC`. |
| A library is not found when the program starts | It was linked from a folder the system doesn't search. Run inside the same `nix-shell`, or install it system-wide. |
| Windows: `no C compiler found to link with ('clang')` | Put llvm-mingw's `bin` folder on your PATH (section 2). |
| Windows: `finch.exe` doesn't start (missing `libclang.dll`) | Keep `libclang.dll` next to `finch.exe`. |
| macOS: `finch` doesn't start (`Library not loaded: …libLLVM…`) | `brew install llvm@21`. |
| macOS: `can't find the C header 'stdio.h'` | Install the command line tools: `xcode-select --install`. |
| `--target arm64`: `no C compiler found to link with` | Install `aarch64-linux-gnu-gcc` (Debian: `gcc-aarch64-linux-gnu`), or set `FINCH_CC`. |
| `clang` inside `nix-shell` can't find `stdio.h` | You have an old `shell.nix`: `llvmPackages.clang` must be listed before `llvmPackages.libclang`. |
| A program never ends | A loop condition never becomes false. Press Ctrl+C. |
---
## 22. Project layout and tests
```
Finch/
├── src/ the compiler, in Finch (lexer, parser, gen: LLVM IR as text, cimport: libclang, lsp, main ...)
├── std/ the standard library, in Finch: json, path, time, proc, fs, thread
├── runtime/ finch_rt.c: the small runtime (text, arrays, maps, input, files)
├── bootstrap/ build.sh, install.sh and the seeds (the compiler's own IR, one per system)
├── examples/ hello, tour, types, structs, todo, guess, wordcount, c_import, simd, window, opengl, vulkan, raylib, llvm
├── tests/
│ ├── run/ new/ programs + the exact output they must print (.out), input (.in)
│ ├── fail/ new/fail/ programs that must fail, with the expected error in line 1
│ ├── tool/ finch test, finch fmt, the chain of calls, a finch.toml project
│ ├── run.sh the test runner (MEMCHECK=1 also checks memory with valgrind)
│ ├── cross.sh every test built for Windows / ARM64 and run under Wine / QEMU
│ └── lsp_test.py the language server, talked to like an editor does
├── editors/ the VS Code extension (vscode/), Kate highlighting and LSP setup (kate/)
├── docs/ this documentation (en, pl), including the cheat sheet
└── shell.nix the Nix development environment
```
```sh
tests/run.sh # programs, expected errors, -g builds, a shared library, the language server
MEMCHECK=1 tests/run.sh # the same under valgrind: no leaks, no bad memory access
tests/cross.sh windows # every test built for Windows and run with Wine
tests/cross.sh arm64 # every test built for ARM64 Linux and run with QEMU
```
---
## 23. Enums and match
An **enum** is a type with a short list of named values.
```
enum Color { Red, Green, Blue }
enum Status { Ok = 200, NotFound = 404 } // give a variant a number if it needs one
c := Color.Green
print(c) // Green
print(int(Status.NotFound)) // 404
print(Color(2)) // Blue: a number turned into the enum (checked: Color(7) is an error)
```
Variants are written `Color.Red`. They can be compared with `==` and `!=` (not with `<`), used as map keys,
struct fields and array elements, and an enum can have methods like a struct: `fn Color.next() -> Color { ... }`.
**match** picks what to do from a value, instead of a ladder of `if`:
```
match c {
Red -> print("warm") // inside a match on an enum, the bare name is enough
Green, Blue -> print("cool") // several values for one arm
}
size := match n { // as an expression: it gives a value
1, 2, 3 -> "small"
4, 5 -> "medium"
else -> "large"
}
match word {
"quit" -> {
print("bye")
return
}
else -> print("what?")
}
```
`match` works on enums, whole numbers, chars, text and bools. The arms are tried in order. A match on an enum has to
handle **every variant** or end with `else` (the compiler says which are missing); any other match needs `else`.
In an expression, every arm gives the same type.
### Variants that hold values
A variant can carry values. Make one with `Shape.Circle(2.0)`, take them out with `match`, which names them:
```c
enum Shape {
Circle(float)
Rect(float, float)
Point
}
fn Shape.area() -> float {
return match self {
Circle(r) -> 3.14 * r * r
Rect(w, h) -> w * h
Point -> 0.0
}
}
```
`_` ignores a value. `match` must handle every variant (or end with `else`). Printing shows `Rect(2, 3.5)`. Such an enum is
copied and freed like a struct; it can't be compared with `==` (match on it). Variants of these enums can't have numbers.
## 24. Generics
Write a function or a struct once for any type, with the type as a name in square brackets:
```
fn max[T](T a, T b) -> T {
if a > b {
return a
}
return b
}
struct Stack[T] {
[]T items
}
fn Stack[T].push(T x) {
self.items.push(x)
}
print(max(3, 9), max("apple", "pear")) // T is worked out from the arguments
print(max[float](1, 2.5)) // or written out, where it can't be worked out
s := Stack[int]() // a generic struct is always written with its type
s.push(7)
```
Several parameters work (`struct Pair[A, B]`), and so do nested types (`Stack[Pair[int, str]]`). Each use makes its
own copy of the code, so a generic is as fast as code written by hand. There are no constraints: if `T` can't do
what the function does (`a > b` on a struct), the error says so and tells which version it was building, like
`(while building bigger[Point])`.
## 25. The standard library
Modules that come with Finch, written in Finch. `import json` finds `json.fch` next to your file first, and the
library's second, so your own file of the same name wins.
| Module | What it does |
|---|---|
| `path` | `join`, `base`, `dir`, `ext`, `stem`, `parts`, `clean`, `is_abs`: paths as text, separated by `/` |
| `time` | `now()`, `millis()` (a clock for measuring), `sleep(ms)`, `date(t)`, `local(t)`, `format(t, "%Y-%m-%d")`, `days_in_month` |
| `proc` | `run(cmd)` (the exit code), `output(cmd)` (what it printed; an error if it failed), `env(name, fallback)`, `pid()`, `cwd()` |
| `json` | `parse(text)`, `stringify(v)`, `pretty(v)`, and the value type `Json` with `get`, `at`, `set`, `push`, `has`, `str_or`, `int_or` ... |
| `fs` | `read_bytes(p)` / `write_bytes(p, data)` (`[]u8`, any bytes), `exists`, `is_dir`, `mkdir` (with the folders above), `list` (sorted names), `remove_dir`, `data_dir(app)` (a folder for the user's saves) |
| `thread` | `spawn(addr(work), addr(data))`, `t.join()`, `mutex()` with `lock`, `unlock`, `close`, `cpus()` (section 29) |
`min(a, b, ...)`, `max(a, b, ...)`, `abs(x)` and `clamp(x, lo, hi)` work on any numbers without an import (a function of
your own with the same name wins).
```
import json
import path
doc := try json.parse(read_file("config.json"))
name := doc.get("name").str_or("unnamed") // a missing key is null, so chains never fail
doc.set("name", json.new_text(name + "!"))
write_file(path.join("out", "config.json"), json.pretty(doc))
```
## 26. Current limits
Not there yet:
- Interfaces.
- Unicode-aware text: `.len`, `s[i]` and `upper()` work on bytes / ASCII.
- Checks that stop two threads from changing the same array at once (use a mutex).
- Windows on ARM64, 32-bit systems, WebAssembly.
### Warnings about big copies
A struct with 256 bytes or more of plain data (numbers, structs of numbers, C's `[N]T`) is copied byte by byte when it
is a parameter (`fn f(Body b)`) and in `for x in bodies`. Finch says so, and still builds the program:
```
game.fch:27:11: warning: 'b' is a Body (264 bytes), copied on every call to weight; take ptr[Body] to share it instead (callers can still pass the variable as it is)
game.fch:45:5: warning: each Body (264 bytes) is copied into 'x'; for i in 0..bodies.len with bodies[i] works on it in place
```
Text, arrays and maps inside a struct don't count: passing the struct copies only their handles, not what they hold.
`b := a` is not warned about: it says "copy" in the code. A method never copies `self`. Editors show the warnings in yellow.
See the [roadmap in the README](../../README.md#roadmap).
## 27. Tests: test blocks and finch test
Tests go next to the code they test:
```c
fn damage(int hp, int hit) -> int {
return max(hp - hit, 0)
}
test "damage never goes below zero" {
assert(damage(5, 10) == 0)
assert(damage(5, 2) == 3, "got \(damage(5, 2))")
}
```
`finch test game.fch` runs every `test` block of the file and of the modules it imports (not of the standard library),
each on its own: a failed `assert` or any runtime error ends only that test, and the next one runs.
```
ok damage never goes below zero
FAIL loading a level
level.fch:31: runtime error: can't read the file "levels/1.txt"
1 passed, 1 failed
```
The exit code is 1 when a test failed, so CI can use it. `finch run` and `finch build` leave test blocks out.
`assert(cond)` also works outside tests: it stops the program with `assert failed` when `cond` is false.
## 28. A project: finch.toml
A `finch.toml` next to the code says once what would otherwise go on every command line:
```toml
name = "game" # the program finch build makes (from the entry)
entry = "src/main.fch" # used when finch run / build / test get no file
paths = ["src", "lib"] # folders with Finch modules
include = ["vendor/SDL3/include"] # folders with C headers (also for your .c / .cpp files)
links = ["SDL3", "native/glue.cpp"] # libraries, and your own .c / .cpp / .o / .a files
libdirs = ["vendor/SDL3/lib"] # folders with libraries
cflags = ["-DFAST"] # for your .c / .cpp files
ldflags = ["-Wl,-rpath,$ORIGIN"] # for the linker
cpu = "native" # the same as --cpu native (section 31)
[target.windows] # added only when building for Windows
links = ["ws2_32"]
libdirs = ["vendor/SDL3/lib/x64"]
[target.linux-arm64] # system and CPU: windows, linux, macos, android, and -x86_64 / -arm64
libdirs = ["vendor/SDL3/lib/arm64"]
```
Folders are relative to `finch.toml`. Finch looks for it in the file's folder and the folders above, so
`finch run src/tools/editor.fch` inside the project uses it too. Only this much of TOML is read: `[sections]`,
`key = "text"`, `key = ["a", "b"]` (over several lines too), numbers, `true`/`false` and `#` comments.
## 29. Threads
```c
import thread
struct Chunk {
[]float samples
float peak
}
fn analyse(ptr arg) { // a thread's function takes one ptr
ptr[Chunk] c = arg
for s in c.samples {
c.peak = max(c.peak, abs(s))
}
}
fn main() {
[]Chunk chunks = load_chunks()
[]thread.Thread running
for i in 0..chunks.len {
running.push(thread.spawn(addr(analyse), addr(chunks[i])) or {
print(err)
exit(1)
})
}
for i in 0..running.len {
running[i].join() // wait for all of them
}
}
```
What a thread gets is shared, not copied, so:
- it must live until `join()` (here `chunks` lives to the end of `main`);
- don't change the array the threads point into (no `push`) until they are joined;
- what more than one thread changes goes under a lock, or is a number changed with `atomic_...`.
```c
m := thread.mutex()
defer m.close()
m.lock()
log.push("done") // one thread at a time between lock and unlock
m.unlock()
atomic_add(addr(done), 1) // done is an int shared by the threads
n := atomic_load(addr(done))
```
`atomic_add`, `atomic_sub`, `atomic_swap` give back the old value; `atomic_cas(p, old, new)` puts `new` only if `old`
is there and says whether it did. `thread.cpus()` says how many threads the machine runs at once.
## 30. C++, Rust and Android
Finch talks to other languages through C, the way every language does.
**C++**: put what Finch calls behind a C header, with `extern "C"` on the C++ side:
```cpp
// glue.h
#ifdef __cplusplus
extern "C" {
#endif
int physics_step(float dt);
#ifdef __cplusplus
}
#endif
```
and `import "glue.h"` with `link "glue.cpp"` (or `links = ["glue.cpp"]` in `finch.toml`). The `.cpp` file is built with
the C++ compiler (`clang++` or `g++`, or `FINCH_CXX`), which then also links, so the C++ library comes along.
A C++ library that has a C API (Box2D 3, Jolt's JoltC, PhysX's C wrapper) is imported like any C library.
**Rust**: make a `staticlib` and export C functions:
```rust
// Cargo.toml: [lib] crate-type = ["staticlib"]
#[no_mangle]
pub extern "C" fn path_len(x: f32, y: f32) -> f32 { (x * x + y * y).sqrt() }
```
Write the header (`float path_len(float x, float y);`, or let `cbindgen` write it), then `import "pathfind.h"` and
`link "target/release/libpathfind.a"`. On Linux a Rust static library also needs `links = ["dl", "pthread"]`.
**Android**: `finch build game.fch --target android --shared` makes `libgame.so` for 64-bit ARM Android (API 24 and
up) with the NDK's clang, found through `ANDROID_NDK_HOME` (or `FINCH_CC`). Load it from an app, for example through
SDL3's Android project (the library's `SDL_main` is an `export fn`). Thread-local data goes through the emulated TLS
that older Android versions need.
---
## 31. Vectors (SIMD)
A processor can add or multiply several numbers with one instruction: 4 floats at once with SSE, 8 with AVX2, 16 with
AVX-512, 4 with ARM's NEON. That is SIMD (single instruction, multiple data), and it is how image filters, audio, physics
and neural networks get their speed. In Finch it is a type: **`f32x8` is 8 `f32` side by side**.
```c
a := f32x4(1.0, 2.0, 3.0, 4.0)
b := f32x4(0.5) // 0.5 in every lane
print(a + b) // f32x4(1.5, 2.5, 3.5, 4.5)
print(a * 2.0) // a plain number counts for every lane: f32x4(2, 4, 6, 8)
print(a.sum(), a[2], a.len) // 10 3 4
```
**The types** are a number type, `x`, and how many lanes: `f32x4` `f32x8` `f32x16`, `f64x2` `f64x4` `f64x8`,
`i8x16` `i8x32` `i8x64`, `i16x8` `i16x16`, `i32x4` `i32x8` `i32x16`, `i64x2` `i64x4`, and the same with `u` (unsigned).
2 to 64 lanes, at most 512 bits in all. A vector is a plain value like an `int`: it goes into variables, struct fields,
arrays, maps and function parameters, and `print` and `str` show it.
**Making one:**
| | |
|---|---|
| `f32x8(x)` | `x` in every lane |
| `f32x8(a, b, c, d, e, f, g, h)` | one number per lane |
| `f32x8(list, i)` | `list[i]` … `list[i+7]` from a `[]f32` (or through a `ptr[f32]`); stops the program if they aren't all in the list |
| `f32x8(v)` | another 8-lane vector converted lane by lane: `f32x8(ints)`, `i32x8(floats)` (cuts the fractions), `i16x8(i32s)` |
**Working with it:**
| | |
|---|---|
| `+ - * /` | lane by lane (`/` on floats only: x86 and ARM can't divide whole numbers lane by lane) |
| `& \| ^ << >> ~` | lane by lane, on whole-number lanes |
| `-v` | every lane negated |
| `v == w`, `v != w` | `true` if every lane is equal / if any differs |
| `v[i]`, `v[i] = x`, `v.len` | one lane; the number of lanes |
| `min(v, w)`, `max(v, w)`, `abs(v)`, `clamp(v, lo, hi)` | lane by lane; `lo`, `hi` and `w` can be plain numbers |
| `v.sum()`, `v.min()`, `v.max()` | all the lanes made into one number |
| `v.dot(w)` | `(v * w).sum()` |
| `v.sqrt()`, `v.floor()`, `v.ceil()`, `v.round()` | every lane (float lanes) |
| `v.fma(b, c)` | `v * b + c` with one rounding, always |
| `v.muladd(b, c)` | `v * b + c` the fastest way here: one instruction where the processor can |
| `v.store(list, i)` | writes the lanes to `list[i]` … (or through a `ptr`) |
`<` and the other comparisons would give an answer per lane, so they aren't there; use `min`, `max` or `v[i] < w[i]`.
**A real loop.** The usual shape: as many whole vectors as fit, then the rest one by one.
```c
fn dot([]f32 a, []f32 b) -> f32 {
acc := f32x8(0.0)
i := 0
while i + 8 <= a.len {
acc = f32x8(a, i).muladd(f32x8(b, i), acc)
i += 8
}
total := acc.sum()
while i < a.len {
total += a[i] * b[i]
i += 1
}
return total
}
```
`examples/simd.fch` times this against the plain loop.
**Which instructions.** By default a program runs on every processor of its system (on x86-64 that means SSE2, so an
`f32x8` is done as two halves). `--cpu native` (or `cpu = "native"` in `finch.toml`) lets LLVM use everything the processor
that builds it has: AVX2 and FMA, AVX-512, or NEON's newer parts on ARM. Such a program needs a processor like that one to
run. To pick a level instead: `--cpu x86-64-v3` (AVX2, most PCs since 2015), `--cpu x86-64-v4` (AVX-512), `--cpu apple-m1`.
Lanes wider than the processor's registers are split by LLVM, and the same code builds for x86-64 and ARM64 alike.
`v.sum()` on float lanes adds them in whatever order is fastest, so the last digits can differ from adding them one by one.