# [RFC] [Discussion] Bound-Erased Generic Types

- Thread: https://externals.io/message/131173
- Original archive: https://news-web.php.net/php.internals/131173
- Started by Frederik Bosch on 2026-06-08T18:47:59+00:00
- Messages: 2
- Reply by email to internals@lists.php.net
- More for agents: search with https://externals.io/search.md?q={query} · JSON API at https://externals.io/api · documentation at https://externals.io/llms.txt

---

## #131173 — Frederik Bosch — 2026-06-08T18:47:59+00:00

URL: https://externals.io/message/131173#131173

> 
> > […]
 >
 > Hello internals,
 >
 > For those not in discord, I spent nearly a week attempting to 
implement reified generics on top of this branch to see how challenging 
it would be.
 >
 > I have a working implementation: 
https://github.com/php/php-src/compare/master...bottledcode:php-src:reify
 >
 > Disclosure: AI assisted in tests and memory leak bug hunts.
 >
 > *The Approach*
 >
 > Classes are monomorphized and similar to Gina's substitution approach 
-- a monomorphized class shares as much memory as the templated class as 
possible, mainly holding its substitutions. This happens during runtime, 
only when it cannot be done at compile time (which is mostly already 
handled by this branch).
 >
 > Functions/methods get bound in call frames and share the substituted 
types from their parent (class, outer closure, etc).
 >
 > *The Result*
 >
 > Given:
 >
 > class Box<T : object> {
 > public function __construct(public T $value) {}
 > public function get(): T { return $this->value; }
 > }
 >
 > The following work as expected:
 >
 > $b = new Box::<DateTime>(new DateTime());
 > $b instanceof Box<DateTime>; // true
 > $b instanceof Box<Request>; // false
 >
 > Whereas the following will generate errors at runtime:
 >
 > $b = new Box::<DateTime>(new Response);
 > $b = new Box::<int>('password 123');
 >
 > *Performance*
 >
 > Importantly, for code that does not use generics: there is no impact 
(generics specific code is skipped).
 >
 > For a call (either `new` or a function/method call) that does use 
generics, the call takes **up to** ~2x a non-generic call. By using 
Seif's PSL library as a test, and converting it to generics, we were 
able to show that benchmarks go about 1.3-1.5x slower when comparerd to 
*no type checking *on the original (ie, `mixed`).
 >
 > Of note, when doing manual type checking with `mixed`, the 
performance cost of generics is roughly the same. That means:
 >
 > function foo<T>(T $val): T {
 > return $val;
 > }
 >
 > function bar(mixed $val): mixed {
 > if (is_int($val)) { return $val; }
 > throw new Exception();
 > }
 >
 > These two functions will have roughly the same performance, with some 
jitter depending on the complexity of the type being checked.
 >
 > I think this is important to point out: if checked generics are 
"about as fast as manual checks", then it behooves us to go for checked 
generics and not erased generics, which would force everyone to type 
check manually. Whether that happens in this same RFC or a later one is 
an open question.
 >
 > *Related Bug*
 >
 > One other issue discovered while working on this branch, the 
following doesn't behave as written with erased generics:
 >
 > try {
 > do_http_call();
 > } catch(HttpError<NotFound> $e) {
 > // ignore
 > } catch(HttpError<Forbidden> $e) {
 > // alert: api key has been revoked
 > }
 >
 > With erased generics, the latter is never hit as it is erased to just 
`HttpError` ... no warning, no error ... it doesn't work as written.
 >
 > To me, that is a massive footgun. Checked generics work as written.
 >
 > *Limited Inference*
 >
 > Secondly, I was able to get limited inference "for free" with this 
approach. This works:
 >
 > class Foo { public string $kind = 'foo'; }
 > class Bar { public string $kind = 'bar'; }
 >
 > function kind<T : object>(T $x): string {
 > return T::class;
 > }
 >
 > echo kind(new Foo) . "\n";
 > echo kind(new Bar) . "\n";
 > // outputs:
 > // Foo
 > // Bar
 >
 > I would appreciate it if others could review the code to verify my 
results and implementation. Assuming it is soound, I believe this should 
resolve issues people have been raising with partially erased generics 
and give us sufficiently complete generics.
 >
 > Whether to write a new RFC or merge into a single RFC is still being 
discussed in discord and open for discussion here.
 >
 > PS. My normal email address is broken, so this is a new email address 
on this list. 👋
 >
 > — Rob


Hi Rob,

Assuming that both reified and bound-erased generics hit the same lines 
of code, and thus that the exception example you make will/can be fixed 
in the bound-erased version, why not offer both versions? Would 
developers not choose to run tests, CI/CD, web development and cli 
applications with the reified generics enabled, while in web production 
they might choose to run with the faster bound-erased mode?

Regards,
Frederik

---

## #131174 — Rob Landers — 2026-06-08T18:59:21+00:00

URL: https://externals.io/message/131173#131174 · In reply to: #131173

> 
> > […]
> implement reified generics on top of this branch to see how challenging 
> it would be.
> > […]
> https://github.com/php/php-src/compare/master...bottledcode:php-src:reify
> > […]
> -- a monomorphized class shares as much memory as the templated class as 
> possible, mainly holding its substitutions. This happens during runtime, 
> only when it cannot be done at compile time (which is mostly already 
> handled by this branch).
> > […]
> types from their parent (class, outer closure, etc).
> > […]
> (generics specific code is skipped).
> > […]
> generics, the call takes **up to** ~2x a non-generic call. By using 
> Seif's PSL library as a test, and converting it to generics, we were 
> able to show that benchmarks go about 1.3-1.5x slower when comparerd to 
> *no type checking *on the original (ie, `mixed`).
> > […]
> performance cost of generics is roughly the same. That means:
> > […]
> jitter depending on the complexity of the type being checked.
> > […]
> "about as fast as manual checks", then it behooves us to go for checked 
> generics and not erased generics, which would force everyone to type 
> check manually. Whether that happens in this same RFC or a later one is 
> an open question.
> > […]
> following doesn't behave as written with erased generics:
> > […]
> `HttpError` ... no warning, no error ... it doesn't work as written.
> > […]
> approach. This works:
> > […]
> results and implementation. Assuming it is soound, I believe this should 
> resolve issues people have been raising with partially erased generics 
> and give us sufficiently complete generics.
> > […]
> discussed in discord and open for discussion here.
> > […]
> on this list. 👋
> > […]
> 
> 
> Hi Rob,
> 
> Assuming that both reified and bound-erased generics hit the same lines 
> of code, and thus that the exception example you make will/can be fixed 
> in the bound-erased version, why not offer both versions? Would 
> developers not choose to run tests, CI/CD, web development and cli 
> applications with the reified generics enabled, while in web production 
> they might choose to run with the faster bound-erased mode?
> 
> Regards,
> Frederik

It's not just about "speed" -- if you want speed, you can just run your code through a script and delete types altogether and ship it to production today. PHP does *some* pattern matching *today *based on the types you write: catching exceptions goes to the branch given the specific type. If we want to actually implement pattern matching, we need the full type, not the erased one. If you ever want typed arrays/structs that actually prevent you from adding "foobars" to an array of ints ... you need the full type.

Erased generics don't prevent typos, they don't actually enforce correctness, nor do they provide readability. The latter one being the worst offender, in my humble opinion. I write code expecting it to do what it says, and PHP has been moving in that general direction for over a decade. It seems unreasonable to take a step back, for "speed".

— Rob
