Read this in Italiano.
🌐 Official website: phprust.com
📊 Live test coverage & PHP compatibility — parity with the official PHP test suite, area by area.
PHP, reimplemented from scratch in Rust. A modern, memory-safe, async-ready PHP 8.5 runtime — driven by observable behavior, not by the internal architecture of the Zend Engine.
phpr script.php # a drop-in for `php`, but it's Rust all the way downThe Zend Engine — the heart of PHP — is ~270,000 lines of C that have piled up since 1999. It carries manual memory management, a custom garbage collector, a thread-safety layer (TSRM), a macro-generated VM, and a convoluted JIT. It's battle-tested but brittle: entire classes of vulnerabilities (use-after-free, buffer overflow) live there by construction.
The insight behind the project is to flip the problem on its head:
The contract worth preserving isn't Zend's design, but PHP's observable output.
And that output already has a perfect oracle: the ~21,500 official .phpt tests shipped with
the PHP source. Any runtime that produces the exact same output is PHP. This turns the job from
"translating C" into "spec-driven reimplementation", where you only read the C to pin down the
semantics in the ambiguous cases.
The result is an engine where Rust does the heavy lifting at zero cost: ownership replaces
zend_alloc, Rc+copy-on-write replaces manual refcounting, Send/Sync make multi-threading a
property of the type instead of a subsystem (TSRM), and a resident process makes the engine
async-ready by construction.
A PHP runtime that is, in order:
- Faithful — bug-for-bug compatible with PHP 8.5 on the official
.phptcorpus (including the quirks of type juggling, legacy warnings, and byte-identical stack traces). - Safe — no segfaults at the core level; the C memory-bug classes eliminated by Rust's type system.
- Modern — shippable as a single binary (the Go/Deno effect), with a built-in native web server and a natively async, multi-threaded foundation — moving past PHP's historical shared-nothing / single-threaded limitation.
The real test bench isn't a microbenchmark: it's running Composer and then serving a Hello World route on Laravel/Symfony. Those milestones stress OOP, autoloading, and Reflection far more than any synthetic test.
| Phase | Milestone | Status |
|---|---|---|
| 1. Semantic core | Type juggling faithful to the oracle (zend_operators.c), ==/===, coercions |
✅ Done |
| 2. Full language | Expressions, control flow, functions, arrays, references, closures | ✅ Done |
| 3. OOP | Classes, inheritance, visibility, static/LSB, magic methods, enums, traits, interfaces |
✅ Done |
| 4. Exceptions & errors | try/catch/finally, catchable engine errors, stack traces, line tracking |
✅ Done |
| 5. Bytecode VM | Generators, yield from, Fibers on explicit frames — no unsafe, no stackful coroutines |
✅ Done |
| 6. Memory | Cycle collector for circular references (the other big "dragon") | ✅ Done |
| 7. Standard library | 785 registered internal functions (measured): array/string/math/json/preg/mbstring/hash/file/stream/date/session… | ✅ Substantial (long tail in progress) |
| 8. Real Composer | composer require monolog/monolog end-to-end: resolution, HTTPS download (rustls), unzip, autoload — and the package runs |
✅ Done |
| 8b. Real ecosystem | PHPUnit 13 green, byte-identical (incl. process isolation); Doctrine DBAL 3769 tests / 0 err / 0 fail on native PDO+sqlite; ORM 3484 tests / 3 err / 13 fail; symfony http-foundation full suite, http-kernel CLOSED: 1663 tests, 0 err / 0 fail; Monolog, collections, inflector, instantiator… | ✅/🔄 In progress |
| 9. Framework bootstrap | Hello World on Laravel / Symfony | ⏳ Next |
| 10. Async & single-binary | Tokio event loop + resident Axum web server, standalone distribution | ⏳ Future |
| 11. JIT (Tier 3) | Clean bytecode → Cranelift/LLVM for on-the-fly machine code | 🔭 Vision |
A single production engine: a bytecode VM. Source flows through
parser (mago) → AST → HIR → bytecode → VM dispatch loop. (The project started with a
tree-walker, later removed once the VM reached full parity: see
HISTORY.md.)
php-rust/crates/
php-types Zval / PhpStr / PhpArray / Object + operators (the soul of PHP:
type juggling, full-port from zend_operators.c). Zero internal dependencies.
php-runtime HIR + lowering from `mago`, and the bytecode VM:
compile.rs (HIR→bytecode) + vm/{mod,exceptions,coroutines,arrays,oop,calls}.rs
php-builtins registry of pure value builtins (var_dump, array_*, sprintf, json_*, preg_*,
mb_*, hash/encoding, file/stream, …); together with the VM-side host
builtins (reflection, callable, PDO/sqlite, dom/xml, curl, proc_open,
session, …) phpr registers 785 internal functions (measured by probe)
php-cli the `phpr` binary — drop-in for `php`, CLI-faithful streams + faithful exit code
php-server native web server (Axum + Tokio) — the bridgehead toward async
phpt-runner runs the official `.phpt` tests with capability scan and unified diff vs oracle
diary/ methodological journal: 00-reconnaissance … 99-conclusions + metrics
Why Rust collapses Zend — the structural payoff, in numbers (all LOC
measured with wc -l on PHP 8.5.7 and on this repo, 2026-07-13):
| Zend subsystem | C LOC | Rust replacement | Rust LOC |
|---|---|---|---|
Generated VM + zend_execute.c |
~136,000 | bytecode VM (single engine): php-runtime/src/vm/ |
~37,300 |
zend_compile.c (AST→opcodes) |
~12,400 | HIR + lowering + compiler (hir.rs, lower/, compile/) |
~15,200 |
| re2c lexer + Bison parser + AST | ~23,400 | mago dependency; the AST→HIR bridge is counted in the lowering row above |
— |
zend_alloc / zend_gc / TSRM / opcache (incl. JIT) / win32 |
~108,000 | ownership, Rc+COW, Send/Sync + a cycle collector |
~1,000 |
zend_operators.c + numeric strings (type juggling) |
~3,900 | faithful full-port (ops.rs, convert.rs, numstr.rs) |
~1,700 |
~270K LOC of core Zend C (extensions not counted) → ~67K LOC of engine Rust
(php-runtime + php-types) — a ~4:1 engine-vs-engine collapse. The whole
project today is ~96K LOC of Rust plus ~5.7K of PHP prelude — and that
total also includes the standard library, PDO/sqlite, dom/xml, TLS and the
phpt tooling, functionality that on the C side lives in ext//sapi/ and is
not part of the 270K.
The core language is complete and faithful: all of control flow, functions, arrays, the
reference system, closures, full OOP (classes, inheritance, visibility, static + late static
binding, magic methods, enums, traits, framework-grade Reflection), exceptions (including
byte-identical stack traces and catchable engine errors), generators and Fibers — the
latter implemented by parking frames on an explicit VM stack, with no unsafe and no stackful
coroutines. The hard parts of modern PHP are here too: PHP 8.4 property hooks and lazy
objects (ghost/proxy), first-class callables, strict_types resolved per-unit from the call site.
But the real leap is that the real ecosystem runs:
- Composer installs packages end-to-end: resolution, native HTTPS download (ureq + rustls), native unzip, autoloader dump — and the installed package executes.
- PHPUnit 13.2 boots and produces output byte-identical to the oracle.
- Doctrine DBAL: 3769 tests, 0 errors, 0 failures — on a native Rust implementation of PDO / pdo_sqlite / ext-sqlite3 (bundled rusqlite, with SQLSTATE/errmode/metadata semantics verified one by one against the oracle).
- Doctrine ORM: 3484 tests, 3 errors / 13 failures (and falling) — hydration, UnitOfWork, XML mapping included. Collections, inflector, lexer, event-manager, instantiator: green.
- Symfony: http-foundation full suite (0 errors without its server-bound tests), http-kernel CLOSED — the full 1663-test suite at 0 errors / 0 failures, parity with the oracle (DI container compiles, dumps and reloads; real IANA timezones; Zend-faithful destructor timing); String / Console / Process green.
- Extensions modeled without C:
pdo,pdo_sqlite,sqlite3,dom,libxml,simplexml(on the Rust DOM),curl(easy-API on ureq),openssl/TLS (rustls),zip,mbstring,pcre(3 engines),hash,json,pcntl,posix,ctype,session,zlib(system FFI),bcmath,gmp,tokenizer.
Two of the three historical "dragons" of a PHP port have already been tamed:
- 🐉 Circular references → a Zend-style cycle collector (possible-roots algorithm), with O(candidates) sweep: a pathological test of 87,380 cyclic objects went from ~11s to ~0.25s.
- 🐉 Bug-for-bug compatibility → the entire strategy is anchored to the
.phptcorpus and the real framework suites, the only real shield against regressions.
The third dragon — the C extension ecosystem (PECL) — is being tackled by targeted native rewrites (PDO/sqlite, dom/simplexml, and curl have already fallen that way); a compatibility FFI layer remains the long-term option for the tail.
Fidelity (at HEAD 8018850, 2026-07-14): differential type-juggling vs real PHP at
0 mismatches (37,835 cases — this is the operator differential, a metric distinct from the
.phpt corpus); 1,550 green Rust unit/integration tests; on the official Zend/tests corpus
2,493 phpt pass (61.5% of the runnable ones, growing every session, with a "zero pass→fail by
name" gate on every commit); ~860 commits of history tracked session by session.
The detailed history of the ~70 build steps lives in HISTORY.md; the replicable methodological journal is in diary/.
- WordPress — the new primary target (roadmap reordered: WP before Laravel).
symfony/http-kernel is done (0/0) and wp-cli already runs end-to-end from source
(
wp --infoat oracle parity). Next:wp core download+ the official SQLite integration plugin (runs on the already-green PDO/SQLite), then a real server SAPI, thenmysqliand media (gd/exif/zip). Plan:php-rust/NEXT_SESSION_WORDPRESS.md. - Doctrine ORM to zero — down to 3 errors / 14 failures; the remainder is triaged
(XSD
schemaValidate, lazy-proxy edges). - Framework bootstrap — Hello World on Laravel / full Symfony kernel: the ultimate stress test for autoloading and Reflection, within reach now that the DI container runs.
- Robustness — convert user-input-reachable
unwrap/expectinto typed VM errors + fuzz thelower/compilepipeline, for a no-panic guarantee. - The async leap — integrate a Tokio event loop and consolidate
php-server(Axum) into a resident runtime, toward a natively concurrent PHP and a shippable single binary.
cd php-rust
cargo run -p php-cli -- script.php # run a script with `phpr`
cargo test # unit + integration tests
# Differential vs oracle (requires a php binary; auto-skips if absent):
PHP_ORACLE=/path/to/php cargo test -p php-types --test differential
# Run the official .phpt corpus through the VM:
cargo run -p phpt-runner -- /path/to/php-src/tests /path/to/php-src/Zend/tests
cargo run -p phpt-runner -- --isolate --list-fails <path> # one test = one sub-process, with diffDiagnostics: PHP_RUST_TRACE=hir|body|exec|all phpr script.php prints the lowered HIR and/or the
execution trace to stderr, without polluting the stdout compared against the oracle.
The idea of "rewriting PHP in Rust to make it async and safe" is a magnet for the Rust community.
The best way to contribute once you've found your footing: pick a missing builtin or a group of
failing .phpt tests (phpt-runner --list-fails), reproduce them against the oracle, and close
the gap while staying byte-identical. The project's golden rule: the oracle is always right.
MIT.