# minc — guidance for AI coding agents

This file is read automatically by Claude, Cursor, Codex, Aider, and
other agent tools when it sits in a project's root directory. Drop a
copy of this file into any project that uses minc and the agent will
follow the conventions below without further prompting.

## What is minc

minc is a small C replacement that compiles `.mc` source files
directly to native binaries on every supported target. Targets: 
Windows x64 (PE), Linux x64 / arm64 (ELF), macOS arm64 (Mach-O), 
iOS arm64 (Mach-O), Android arm64 (ELF), and WebAssembly.

The standard library is intentionally minimal. There is no `stdio.h`,
no `string.h` — the compiler ships built-in I/O and basic utilities,
plus a small `lib/` of opt-in modules (math, file I/O, sokol bindings,
etc.). Programs `import` library modules; they don't `#include <…>`.

## Read first

`LANGUAGE.md` (next to this file in the deploy zip) is the full
language reference. Read it once before writing minc code. The rest
of this file assumes you have, and focuses on the patterns and
shapes that real code uses.

## Differences from C

minc looks like C and most C reasoning carries over, but a handful
of differences matter at every keystroke. If you default to C
habits without checking these, the result will compile-error or
just look wrong.

**Things minc has that C does not:**

- **Function overloading by parameter type.** `dot(float2, float2)`
  and `dot(float3, float3)` can both exist; the call site picks by
  exact-type match (no implicit conversions used to disambiguate).
  Use this — it's how `lib/linear.mc` exposes the same op for
  different vector widths.
- **`defer stmt;` for LIFO cleanup at block exit.** Use it instead
  of trailing `goto cleanup:` chains. Runs on every return path,
  including early ones.
- **`when os(linux) { ... }` / `when arch(arm64) { ... }`** for
  compile-time conditional code. Replaces `#ifdef`. Conditions can
  also be `when defined(NAME)` for `-DNAME=1` build flags.
- **`import name;`** brings in a `lib/<name>.mc` module. There are
  no header files; declarations and definitions live in the same
  file. C's `#include` does not exist for user code.
- **`var x = expr;`** infers the type from the right-hand side.
- **`p.field` auto-dereferences pointers** — write `p.x` not
  `p->x`. The `->` operator is not in the language.
- **`new(T)`** allocates a zero-initialised `T` on the heap. No
  `malloc + memset` dance.
- **`noinit T[N] arr;`** declares a stack array without zero-fill.
- **Struct literals** at the use site — `Point{3, 4}` (positional)
  and `sg_color{ .r = 1.0f, .g = 0.5f, .b = 0.2f, .a = 1.0f }`
  (named-field).
- **Variable destructuring** — `var (a, b) = pair_returning_fn();`.
- **Slices** — `T[]` is a length-carrying fat pointer; `T[N]` is a
  fixed array. C's "array decays to pointer" rule does not apply.
- **`extern "lib.dll" T name(...);`** for FFI — the library is
  named at the declaration, not at link time. When grouping
  several declarations against the same library, use the block
  form: `extern "libc.so.6" { i32 socket(...); i32 bind(...); }`.
  The single-line form is fine for one-off declarations.
- **Built-in vector and matrix types.** `float2`, `float3`, `float4`,
  `float4x4`, `int2`/`int3`/`int4`, `f64x2` — first-class, with
  swizzles (`v.xyz`), constructor literals, and arithmetic
  operators. `+ - * / dot cross length normalize` are overloaded
  across them. Operations on `float4`/`float4x4`/`f64x2` lower to
  SSE / AVX / NEON SIMD instructions; you don't write intrinsics by
  hand for the common cases.
- **`@shader` functions** — write a vertex / fragment / compute
  shader inline as a regular minc function and the compiler emits
  the matching HLSL / GLSL / MSL text and metadata for sokol_gfx.
  Same syntax, same type system, no separate `.hlsl` / `.glsl` /
  `.metal` files. See `lib/shader.mc` and the `examples/sokol_*.mc`
  examples.

**Things minc does not have that C does:**

- **No undefined behavior.** Signed overflow wraps; divide by zero,
  null deref, and out-of-bounds indexing all trap (or crash
  cleanly). The compiler does not exploit "this can't happen
  because UB" reasoning to delete code. Don't write defensive
  guards just to "avoid UB" — write the obvious code and trust it.
- **No preprocessor.** No `#define`, no macros, no token pasting.
  Use `const` for constants and `enum` for sets of integers.
  `import` replaces `#include`. `when` replaces `#ifdef`.
- **No header files.** One source file = one translation unit; the
  module's external surface is whatever's not inside `private { }`.
- **No fall-through in `switch`.** Every case is implicitly
  terminated. Multi-value cases use comma syntax: `case 1, 2, 3:`.
- **No implicit narrowing.** `i32 x = some_i64;` is an error;
  write `i32 x = cast(i32, some_i64);`. Implicit widening (i32→i64,
  i32→f64, u8→i32, etc.) is allowed and expected.
- **No mixed-sign arithmetic without a cast.** `i32 a = 5; u32 b =
  6; a + b;` is an error, not a warning. Cast one side explicitly.
  Integer literals are exempt.
- **No `void*` punning of strict pointer types.** Pointer types are
  enforced; round-tripping requires explicit casts.
- **No C-style `static` for file-scope linkage.** Use `private { }`
  blocks instead.
- **No null-terminated strings as a primitive type.** Strings are
  byte slices with an explicit length. The standard library doesn't
  assume `\0` termination.

**Subtle behavior shifts to keep in mind:**

- **Bounds checks on by default.** `arr[i]` traps on out-of-range
  in debug AND release builds unless the compiler is invoked with 
  `--unchecked`.
- **`null` is a keyword, not `0` or `NULL`.** Pointer comparisons
  and assignments use `null`.
- **`bool` is a first-class type**, not `int` masquerading. `if x`
  requires `x` to be `bool`; integers don't auto-convert.
- **Switch cases need braces.** `case X: { ... }` — never bare
  statements.
- **Globals are zero-initialised by default.** `noinit` opts out
  (saves on-disk size by routing the slot to BSS).

**On performance:** the compiler runs solid local optimisations —
constant folding, register allocation, CSE, LICM, strength
reduction, loop unrolling, FMA fusion, the SIMD lowering mentioned
above. Well-formed code reaches MSVC `/O2` and `clang -O2` parity
on most workloads. Don't expect the deeper transformations a much
larger compiler might pull off (auto-vectorising arbitrary scalar
loops, whole-program devirtualisation, profile-guided layout):
write code in a shape that's already close to the machine, and
the compiler will keep it tight.

## Style

### Use modern syntax

- Unary minus: `-x`, never `0.0f - x` or `0 - x` for a value.
- Array initializers: `f32[4] v = { 1.0f, 2.0f, 3.0f, 4.0f };`.
  Don't emit one assignment per element unless the values are
  computed in a loop.
- `for i32 i = 0; i < N; i++` — postfix `++` / `--`, not `i = i + 1`.
- Float math on f32 values uses the `f`-suffixed builtins (`cosf`,
  `sinf`, `sqrtf`, `powf`) so the result stays f32 without an
  explicit cast back.
- `noinit T[N] arr;` when seeding the array immediately afterward;
  skips the zero-fill the language otherwise inserts.
- Hex / decimal literals coerce to any integer type — write
  `u32 rng = 0x9E3779B9;`, not `cast(u32, 0x9E3779B9)`.
- `sizeof(x)` returns `i64`; don't wrap it in `cast(i64, ...)`.
- `alloc<T>(n)` is the generic form — returns `T*` directly.
  Write `u8* buf = alloc<u8>(n);`, never `cast(u8*, alloc(n))`.
- Type-inferred locals: `var x = expr;` when the type is obvious
  from the right-hand side.
- Struct literals: positional `Point{3, 4}` and named-field
  `sg_color{ .r = 0.1f, .g = 0.2f, .b = 0.3f, .a = 1.0f }`.
- Block-form externs when grouping multiple declarations against
  the same library: `extern "libc.so.6" { i32 socket(...);
  i32 bind(...); ... }` — the library name appears once instead
  of repeated on every line. The single-line `extern "lib" T
  name(...);` form stays fine for one-off declarations.
- `defer` for cleanup at block exit (LIFO ordering).

### Avoid

- Unnecessary `cast(...)` when implicit widening covers the
  conversion (i32→i64, i32→f64, u32→i64, u8→i32 — see the
  type-system section in LANGUAGE.md for the full list).
- Per-platform `when os(...) { ... }` blocks at app level when the
  underlying library can absorb the difference. Push the platform
  knowledge into a helper.
- Allocating heap arrays for short-lived data when a fixed-size
  stack array fits.
- Reaching for `cast(T, ptr)` to convert between pointer types when
  the type system already accepts the assignment.

### Comments

- Brief, neutral, declarative.
- Explain *why*, not *what* — the code already says what.
- Don't reference past sessions, fixes, or PR numbers ("we changed
  this because…", "fix for #42") — that belongs in the commit log.
- Don't apologise or hedge ("this could probably be improved",
  "ideally we'd…") — either fix it or leave it silent.
- Skip the comment entirely if removing it wouldn't confuse a reader.

## Canonical example

A small program that exercises the patterns this file describes —
file I/O, buffer math, struct, defer cleanup, no platform code:

```mc
import file;

struct Sample {
    f32 t;
    f32 amp;
}

i32 main() {
    File* f = file_open("samples.bin", FILE_READ);
    if f == null { return 1; }
    defer file_close(f);

    i64 n_bytes = file_size(f);
    i32 n = cast(i32, n_bytes / sizeof(Sample));
    if n <= 0 { return 2; }

    noinit Sample[1024] buf;
    if n > 1024 { n = 1024; }
    file_read(f, &buf, cast(i64, n) * sizeof(Sample));

    f32 peak = 0.0f;
    for i32 i = 0; i < n; i++ {
        f32 a = buf[i].amp;
        if a < 0.0f { a = -a; }
        if a > peak { peak = a; }
    }

    print("peak amplitude: ");
    print_f32(peak);
    print("\n");
    return 0;
}
```

The shape — top-level imports, structs, a `main()` returning `i32`,
`defer` for cleanup, a stack-allocated `noinit` buffer, a typed
loop with `i++`, modular helpers from `lib/` — generalises to any
program. Look in the `examples/` directory of the deploy zip for
real working programs that follow this pattern at larger scales.

## Where to look

- `LANGUAGE.md` — full language reference (types, control flow,
  metaprogramming, FFI, target intrinsics).
- `examples/` — apps that demonstrate the patterns above. Mandelbrot
  ASCII demo, sokol graphics apps, etc. Each one is a single `.mc`
  file that compiles to a working binary.
- `lib/` — the opt-in standard library: `math`, `file`, `linear`,
  `sokol_all`, etc. Read the source if you need to know what's
  available; the modules are small and documented inline.
- `bench/` — comparable minc + C implementations of common
  workloads. Useful as performance references.

## When in doubt

Pattern-match on the existing apps in `examples/` — they are the
canonical reference for both syntax and style. If a pattern shows up
in three or more apps, it's idiomatic. If you can't find it there,
it probably isn't supported (or isn't preferred).
