Language reference

Everything HolyPHP adds on top of PHP — and everything it borrows from Rust. All snippets are verified against the compiler.

Variables & types

Variables are PHP-style: start with $, no declaration keyword, inferred types. Add an explicit type after the name when you want one.

$count = 5;                  // inferred int
$pi = 3.14159;               // float
$ok = true;                  // bool
$who = "Elias";              // string

$count: int = 5;             // pinned types
$rate: float = 0.19;
$name: string = "Ann";
$flag: bool = false;

$items = [1, 2, 3];          // list array
$user = ["name" => "Elias",  // map array (string keys)
         "lang" => "HolyPHP"];
TypeLiteralsNotes
int42, 0x2A64-bit signed
float3.14, 1e964-bit IEEE
booltrue / false
string"..." with {$var} interpolationimmutable, refcounted
array[1, 2], ["k" => "v"]copy-on-write value semantics
mixedanythingruntime-tagged hval

Functions

Fully typed signatures with optional parameters and defaults. Untyped functions are inferred like scripts.

function greet(string $who): string {
    return "Hi, {$who}!";
}

function pow2(float $base, int $exp = 2): float {
    return $base ** $exp;
}

// by-reference parameter (a mutable borrow)
function bump(int &$r): void { $r += 1; }

$n = 41;
bump($n);        // $n === 42

Operators

GroupOperators
Arithmetic+ - * / % **
String. concat, .= append
Comparison== != === !== < > <= >= <=>
Logic&& || !
Bitwise& | ^ << >>
Null coalesce??
Compound+= -= *= /= %= .= **=
Increment++ -- (prefix and postfix)
Ternarycond ? a : b

Control flow

if ($n < 2) { return $n; }
elseif ($n == 2) { echo "two\n"; }
else { echo "many\n"; }

while ($i < 10) { $i++; }

for ($i = 0; $i < 10; $i++) { echo $i; }

foreach ($nums as $x) { $total += $x; }
foreach ($user as $k => $v) { echo "{$k} => {$v}\n"; }

match — algebraic switching

function classify(int $n): string {
    return match ($n) {
        0 => "zero",
        1, 2, 3 => "small",
        _ => "big",
    };
}

Classes & OOP

Visibility is explicit: pub (public), private, static for fields. Methods inherit PHP-style; constructors may be inherited too.

class Counter {
    pub int $count = 0;          // field with default
    private $secret = "s";

    pub function __construct(int $start = 0) {
        $this->count = $start;
    }
    pub function bump(): void {
        $this->count += 1;
    }
    pub function get(): int {
        return $this->count;
    }
}

$c = new Counter(10);
$c->bump();
echo $c->get();      // 11
class Animal {
    pub function speak(): string { return "..."; }
}
class Dog extends Animal {
    pub function speak(): string { return "Woof!"; }
}
echo (new Dog())->speak();   // "Woof!" — dynamic dispatch

Closures & captures

Closures capture PHP-style with use. By-value copies by default; use (&$x) captures by reference — the variable lives in a heap box shared with the outer scope.

$base = 100;
$addBase = function(int $x) use ($base): int {   // by value
    return $base + $x;
};

$clicks = 0;
$onClick = function() use (&$clicks): void {     // by reference
    $clicks = $clicks + 1;                       // outer $clicks changes
};
$onClick(); $onClick();
echo $clicks;    // 2
Why it's safe: the compiler boxes the captured variable and forces it to mixed, so the same slot is shared — no dangling pointers, no data races, verified by hphp check.

Exceptions

try {
    throw "boom";
} catch (string $e) {
    echo "caught: {$e}\n";
} finally {
    echo "always runs\n";
}

throw accepts a value (strings today, Exception objects too). Handlers are invoked through a setjmp-free architecture — the runtime runs each protected block as a helper, so no volatile tricks appear in generated code and -O2 is safe.

Enums

enum Farbe: int { Rot, Gruen, Blau }

function colorName(Farbe $f): string {
    return match ($f) {
        Farbe::Rot   => "red",
        Farbe::Gruen => "green",
        Farbe::Blau  => "blue",
    };
}

Memory model

HolyPHP is memory-safe by default and low-level when you ask.

ConceptSyntaxMeaning
Value semanticsplain assignmentscalars & arrays copy-on-write
Immutable borrow&$xread-only alias, many allowed
Mutable borrow&mut $xexactly one at a time
Owned boxown<T>exclusive ownership, moves
Shared pointerRc<T>refcounted shared ownership
Raw pointer*T / &$x under unsafeonly inside unsafe {}
// borrow checker at compile time:
function laenge(string $text): int {     // takes an immutable borrow
    return strlen($text);
}
$w = "borrowed";
echo laenge($w);        // owner keeps $w

// this is a compile ERROR (two mutable borrows):
// function bump(int &$r): void { $r += 1; }
// bump(&$n); bump(&$n); ...  => rejected by hphp check
unsafe {
    $geheim: int = 42;
    $zeiger: &int = &$geheim;      // raw pointer
    echo "deref = " . (*$zeiger) . "\n";
}
unsafe: everything outside the braces is still checked; inside, you take responsibility for aliasing — exactly Rust's contract, minus the ceremony.

Imports & the stdlib

Libraries ship inside the compiler binary — import finds them with no files on disk.

import "ui.hphp";        // desktop GUI toolkit
import "websocket.hphp"; // RFC 6455 server

Strings

Immutable and refcounted; interpolation is compile-time parsed into concat calls.

$name = "Elias";
echo "Hi {$name}, you have " . (3 + 4) . " new messages\n";

$s = "Hello";
$s .= " World";          // append (rebuilt behind the scenes)

Arrays & maps

One type for both: lists keep insertion order; assigning a string key or a non-sequential int key promotes to a keyed map (exactly PHP's semantics).

$nums = [5, 3, 8, 1, 9];
$nums[] = 7;                 // append
sort($nums);                 // in place
echo $nums[0];               // 1

$m = [];
$m[1] = "a";                 // key 1 is remembered
$m["name"] = "Elias";        // becomes a keyed map
foreach ($m as $k => $v) {
    echo "{$k} => {$v}\n";   // 1 => a, then name => Elias
}

$a = [1, 2, 3];
$b = $a;                     // COW: shares until one writes
$b[0] = 99;                  // clones now; $a unchanged