[PHP-DEV] [RFC] [Discussion] Bound-Erased Generic Types

On Tue, Jun 9, 2026, at 09:26, Brent Roose wrote:

Hi Rob

Very interesting. If this is what it takes to get enough people on board, then so be it.

On a personal note, but don’t let this prevent you from further exploring this option: I hope that at some point we get a way to opt out of runtime type checks (generics or types in general). A 2x performance penalty is a serious issue.

To reiterate what I’ve said before: generics aren’t a runtime tool. Their value comes from static analysis and reflection for meta programming, and I see no reason why generic code should be type-checked at runtime when it has already been type-checked before. As both a PHP user and a representative for one of PHP’s most used static analysers, I want three things: a proper spec, proper syntax, and proper reflection. All these problems are solved with the current RFC; without runtime type checking, and without performance penalties.

Maybe this is just a necessary process for PHP to go through; and who knows, in a couple of years, practical experience will have shown and convinced enough people that it’s unnecessary. I’ll happily deal with the performance overhead and will continue to hope for an opt-out mechanism in the future.

Thanks for the work and effort, really interesting!

Brent

Please remember to “bottom post” (put your reply at the bottom, not the top, of the email).

A 2x performance penalty is a serious issue.

This is a mischaracterization. To be more clear, the call itself approaches a limit of 2x – it isn’t exactly 2x, and not all generic calls are always 2x. I even gave real examples in my email showing real world generic heavy code is only 30-50% slower. And this is without any real optimization work. This is the ceiling, not a final result. Further, it appears to bear the same cost as manually type checking the generics.

I see no reason why generic code should be type-checked at runtime when it has already been type-checked before

PHP does type checks at runtime because it cannot be type-checked before. There is no point where the compiler can be sure about types statically. Each file is compiled at a time, and some parts of compilation even need to be deferred to runtime. This isn’t a limitation of PHP, it’s how you can have two files with the same class name in them and then load different ones depending on capabilities/execution. (ie, loading generated mocks instead of the real class or loading a php 8.3 version instead of a php 9 version of the class).

I know of absolutely zero static analysis tools that understand PHP’s dynamic loading system – they all assume a PSR-4 (or similar) system for autoloading. Thus, only in certain kinds of cases are they actually useful.

All these problems are solved with the current RFC; without runtime type checking, and without performance penalties.

I disagree, wholeheartedly. But that is likely because during my work, I had to actually fix tests from the erasure branch that “looked” right but were quite wrong. As I mentioned in my email, the cost turns out to be nearly exactly the same as manual type checks. The performance issue is a mirage if you want to do any type assertions – and you probably do. As I mentioned in my last email, you can simply write a script today to remove all types in your code. There’s nothing stopping you from doing that to get some “speed”.

— Rob

On Tue, Jun 9, 2026, at 10:30 AM, Rob Landers wrote:

A 2x performance penalty is a serious issue.

This is a mischaracterization. To be more clear, the *call itself*
approaches a limit of 2x -- it isn't exactly 2x, and not all generic
calls are always 2x. I even gave real examples in my email showing real
world generic heavy code is only 30-50% slower. And this is without any
real optimization work. This is the ceiling, not a final result.
Further, it appears to bear the *same *cost as manually type checking
the generics.

Also, as noted, the cost is born only when a generic type is checked. The impact on non-generic code is, seemingly, zero. That is important.

I see no reason why generic code should be type-checked at runtime when it has already been type-checked before

PHP does type checks at runtime because *it cannot be type-checked
before.* There is no point where the compiler can be sure about types
statically. Each file is compiled at a time, and some parts of
compilation even need to be deferred to runtime. This isn't a
limitation of PHP, it's how you can have two files with the same class
name in them and then load different ones depending on
capabilities/execution. (ie, loading generated mocks instead of the
real class or loading a php 8.3 version instead of a php 9 version of
the class).

I know of absolutely zero static analysis tools that understand PHP's
dynamic loading system -- they all assume a PSR-4 (or similar) system
for autoloading. Thus, only in certain kinds of cases are they actually
useful.

All these problems are solved with the current RFC; without runtime type checking, and without performance penalties.

I disagree, wholeheartedly. But that is likely because during my work,
I had to actually fix tests from the erasure branch that "looked" right
but were quite wrong. As I mentioned in my email, the cost turns out to
be nearly exactly the same as manual type checks. The performance issue
is a mirage if you want to do any type assertions -- and you probably
do. As I mentioned in my last email, you can simply write a script
today to remove all types in your code. There's nothing stopping you
from doing that to get some "speed".

— Rob

I will go a step further: There are certain things that PHP *cannot* do nicely in core/stdlib without generics.

Gina's Fetch API proposal is one such case. (Replacing ArrayAccess with a series of smaller interfaces that are more targeted and well-designed.) All of the parameters of those methods would need to be typed `mixed` today, and implementations could not narrow it. If those interfaces are generic, that problem disappears.

Collections is another. I want proper collection objects, badly. :slight_smile: That includes operator overloads, which means they must be done in C/stdlib (and even if we had user-space operator overloads, it would be faster in C rather than building them all atop arrays). That means they cannot rely on 4 slightly different user-space static analyzers for pseudo-generics. They need real generics, in the language, or they'll be largely useless. And no, trying to include stubs for auto-complete that match those 4 slightly different user-space static analyzers is not a solution.

SA-only types are a hack, not a core language feature. That's what I meant with my earlier comments about a first-party SA tool. PHP the language needs to be an "internally complete" solution.

Seif's RFC is a big step forward, with the major issue of introducing type erasure when PHP has never had that before. Rob's add-on seems to resolve that problem. I am not in a position to judge its implementation in detail, but if it passes muster, I think we may have finally found our way forward.

And I am willing to eat some performance for that, as long as it only hits generic code.

--Larry Garfield

Hi Rob

Nikita addresses this here:

···

On 08.06.26 17:13, Rob Landers wrote:

On Sun, May 10, 2026, at 21:02, Seifeddine Gmati wrote:

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

I think it’s worth looking back at Nikita’s comment when he researched generics, and I think it holds today. It touches pretty much on everything discussed.

https://www.reddit.com/r/PHP/comments/j65968/comment/g83skiz/

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).

The main problem with monomorphization is not so much performance (it is theoretically good for performance, and even an otherwise reified generics implementation may wish to monomorphize hot classes for performance reasons), and more about memory usage. It requires a separate class to be generated for each combination of type arguments. If that also involves duplication all methods (which may depend on type arguments), this will need a lot of memory.

Monomorphization as a primary implementation strategy doesn’t make a lot of sense in PHP: It is important for languages like C++ or Rust, where the ability to specialize code for specific types is highly performance critical (and even so code size remains a big problem). In PHP, we will not get enough performance benefit out of it to justify the memory cost (again, when talking about blanket monomorphization). Especially as it’s not clear how it would be possible to cache monomorphized methods in opcache (due to immutability requirements).

The only reason why monomorphization was suggested as an implementation strategy at all is that it would make the implementation of a naive generics model simpler: The premise is that we just need to generate new class entries for everything, and the rest of the engine doesn’t need to know anything about generics. However, this doesn’t hold up once you consider variance for generic parameters (Traversable is a Traversable<int|string>), as such relations cannot really be modelled without direct knowledge of the generic parameters.

With full monomorphized generics, we can discover new variants of a generic class effectively forever. This is especially bad for generic data structures, which is also generics’ biggest use-case. In practice, this means higher memory consumption (incl. higher risk of frequent opcache restarts due to filling shared memory) and more time looking up classes, for no benefit. That said, it’s hard to argue one way or another without concrete numbers, so that should be the first step to progress the conversation beyond what we had 6 years ago.

Secondly, I was able to get limited inference “for free” with this approach.

From my testing on your branch, the type inference is very limited. First of all, Nikita explains why type inference is important to begin with:

Generics are already hard on a purely conceptual level – while we tend to talk about the implementation issues, as these are the immediate blocker, there’s plenty of design aspects that remain unclear. One part that bothers me in particular is the question of type inference:

function test(): List {
// We don’t want to write this:
return new List(1, 2, 3);
// We want to write this:
return new List(1, 2, 3);
}

We certainly wouldn’t want people to write out more types in PHP than they would do in a modern statically typed language like Rust. However, I don’t really see how type inference for generic parameters could currently be integrated into PHP, primarily due to the very limited view of the codebase the PHP compiler has (it only sees one file at a time). The above example is obvious, but nearly anything beyond that seems to quickly shift into “impossible”.

The above example seems to apply to your implementation (while working in Seifeddine’s branch):

class Box {
public function __construct(public T $value) {}
}
// Cannot instantiate generic class Box without type arguments; type parameter T has no default
$box = new Box(42);
// Ok
$box = new Box::(42);

PHP static analyzers are necessarily very capable at static type inference, so this is at least a significant discrepancy between your implementation and a type erasure approach. This actually also questions the gradual typing narrative for type erasure (i.e. “we can add type checks later”), because it’s questionable a reified approach will support the exact set of (correct) programs the erased approach will. That’s especially true because PHP does not have an official static analyzer, and the 3rd party analyzers do practically diverge in the details.

It seems for functions, T is not required to be inferred unless used, likely to circumvent the above limitation. This can lead to confusing cases.

// Works
function a(T $value) { b($value); }
function b(T $value) {}
a(42);

// Breaks (also crashes with ASan due to access to uninitialized memory)
function a(T $value) { b($value); }
function b(T $value) { new Box::($value); }
a(42);

// Works
function a(T $value) { b::($value); }
function b(T $value) { new Box::($value); }
a::(42);

In other words, whether a(42) is safe to call for signature function a<T>(T $value) depends on its implementation, i.e. whether it makes use of T. This is introduces a variation of the function coloring problem, where now all callers must specify generic types recursively. The only way to fix this inconsistency (apart from actual type inference) is to require specifying the type explicitly at all times.

I ran into quite a few other ASan crashes when testing examples, as with PHPs own test suite. Given I’m not sure how complete the implementation intends to be, I won’t go into those any further.

Ilija

Hi Rob,

This is a mischaracterization. To be more clear, the call itself approaches a limit of 2x -- it isn't exactly 2x, and not all generic calls are always 2x. I even gave real examples in my email showing real world generic heavy code is only 30-50% slower. And this is without any real optimization work. This is the ceiling, not a final result. Further, it appears to bear the same cost as manually type checking the generics.

Real engineering, and the implementation is appreciated. But I
disagree that "approaches 2x worst case, 30-50% on real generic-heavy
code" reads as a small cost. Two reasons:

1. Generic-via-native isn't "use it OR keep using @template", once it
ships, the docblock alternative goes away.

Social pressure on every actively-maintained library is to migrate.
Static-analysis tools will gradually downgrade or drop (eventually)
their docblock-generic implementations once the native form is widely
available (this is exactly what happened with attributes vs PHPDoc
annotations after PHP 8.0). The cost isn't paid by libraries that opt
into runtime-reified generics; rather, it's paid by every library that
ships generics at all. Under either proposal, this is essentially
every typed library that exposes a collection, iterable, repository,
result type, or async primitive.

2. The cost compounds through the dependency graph, affecting even
users who don't write generics themselves.

Consider an application that never declares a generic of its own. It
depends on Psl. Psl is generic end-to-end, covering collections,
iterables, results, and options. Application code calling
`Psl\Vec\map($vec, $fn)` pays the reified-check cost on every call,
transitively, even though the application never wrote <...>.

Now a concrete tooling case:
[roave/BackwardCompatibilityCheck](GitHub - Roave/BackwardCompatibilityCheck: 🆎 Tool to compare two revisions of a class API to check for BC breaks · GitHub)
runs as a CI step. It uses its own generics. It depends on Psl. The
honest benchmark isn't "how much slower is one Psl\Vec\map call?",
it's "how much longer does BackwardCompatibilityCheck take to run on a
full codebase once Psl ships native generics and BCC adopts them?"If
the answer is 4 minutes -> 6 minutes per CI run, multiplied across
every PR in every project that uses BCC, that means slower CI, more
compute spend, and longer feedback loops for everyone downstream.

The "30-50% on generic-heavy code" framing also collapses once a user
can't tell which of their dependencies count as "generic-heavy." Under
this RFC, the cost question is "does your code use generics?", zero if
no, small if yes (the substituted-contract checks I just landed sit at
±0.1% benchmark noise). Under universal reification, the question
becomes "do you, or anything in your transitive dependency graph,
declare generic types?", and for an ecosystem where ORM repositories,
collection libraries, async runtimes, HTTP middleware kits, and
standard-library replacements all want generics, the honest answer is
"yes, everywhere."

This isn't an argument against reified generics in principle. It's an
argument that the perf characterisation in the post understates how
the cost will be felt in practice once the syntax is the way to do
generics in PHP. I think the right path is to land this RFC (erased +
substituted contract + reflection, zero cost for non-generic code,
small cost for substituted check sites), let the ecosystem migrate
from docblock to native, and then evaluate reified-via-opt-in on top
of it. Your branch is then the natural starting point for that
follow-up RFC, with much sharper data on what fraction of real PHP
workloads actually carry generic types in their hot path.

If that follow-up shows reified can land at a perf cost the ecosystem
can absorb across the dependency-graph case, great. If it can't, we'll
know, and the cost of finding that out will be much lower if the
erased form is the baseline rather than the starting point.

Cheers,
Seifeddine

Hi Ilija,

From my testing on your branch, the type inference is very limited.
[...]
PHP static analyzers are necessarily very capable at static type
inference, so this is at least a significant discrepancy between
your implementation and a type erasure approach.

A reified implementation should require turbofish at every call site
rather than try to do engine-level inference. The two paths land in
different places here.

For an erased model (this RFC), inference belongs in the
static-analysis layer. The runtime doesn't need to know what T is.
It's been erased to its bound by the time the call happens, so `new
Box(42)` produces a Box object with value typed as `mixed` at runtime.
PHPStan, Psalm, Phan, or Mago can independently decide whether to
infer `Box<int>`, `Box<int|string>`, Box<scalar>, or leave T open at
the static layer, and their disagreement doesn't affect runtime
semantics. That's why this RFC leaves turbofish optional. Forcing it
would be work the engine has no use for. The only callers that need
turbofish under erasure are SA tools when they can't figure out user
intent, same as TypeScript saying "needs explicit type argument" when
nothing in the call site constrains T.

For a reified model, inference becomes an engine problem because the
runtime actually carries the type argument forward. Now `new Box(42)`
has to decide on a concrete T at construction. The clean fix is to
require turbofish at every call site that takes a generic-parametric
type, and reject the call as a compile or runtime error otherwise. No
engine-side inference, ever. The user states intent explicitly. The
engine doesn't guess.

That sidesteps the question of what `new Box(42)` means: under
reification it becomes invalid PHP, and the user writes `new
Box::<int>(42)`.

In other words, whether a(42) is safe to call for signature
`function a<T>(T $value)` depends on its implementation, i.e.
whether it makes use of T. This is introduces a variation of the
function coloring problem, where now all callers must specify
generic types recursively.

Function coloring is a different problem. Coloring (in the async/await
sense) means a property of one function forces the same property on
every function that calls it. If you call an async function, you have
to be async yourself, and so on transitively up the call graph.
Mandatory turbofish doesn't propagate that way. Consider:

function id<T>(T $v): T { return $v; }

function str_id(string $v): string {
    return id::<string>($v);
}

`str_id` is not generic. It supplies the type argument at the call
site and that's the end of it. The "color" doesn't travel upward.
`str_id`'s own signature stays exactly as the author wrote it, with no
generic parameters of its own required. Turbofish is a property of the
call site. The calling function's own signature is unaffected.

The case where you'd add a generic parameter to `str_id` is when
`str_id` legitimately needs to be parametric, forwarding `T` through.
That's a design choice the author makes. The language doesn't impose
it.

The issue you found in Rob's branch is real: half-done inference makes
call-site safety depend on the callee's implementation. That's a
problem with partial inference, not with the call-site syntax. The fix
is the same: require turbofish at every call site, no engine-side
inference. Once the engine stops guessing, call-site safety stops
depending on what the body of the callee does.

TypeScript-style inference for T belongs in the SA layer, where it can
be tool-specific and revisable. The engine should not be in that
business.

Cheers,
Seifeddine

To reply to both of you:

The issue you found in Rob’s branch is real: half-done inference makes
call-site safety depend on the callee’s implementation. That’s a
problem with partial inference, not with the call-site syntax. The fix
is the same: require turbofish at every call site, no engine-side
inference. Once the engine stops guessing, call-site safety stops
depending on what the body of the callee does.

I don’t have enough time today to write out long responses to both your comments. But to this end, scalar inference requires deciding how to handle at the RFC level, not a POC level.

new Box(42);
new Box(42.0);
new Box("42")

In non-strict mode, are these all equivalent? What exactly is T’s type here?

Scalars in PHP … are weird. I wasn’t going to try to define it here.

— Rob

On 9 June 2026 08:26:12 BST, Brent Roose brent.roose@jetbrains.com wrote:

To reiterate what I’ve said before: generics aren’t a runtime tool.

Generics are as much a runtime tool - or as little - as any other type declaration. If it is useful to enforce “function foo(int $bar): int” at runtime, it is equally useful to enforce “function foo(T $bar): T” at runtime.

A mode which erases all runtime checks which can be proven statically would be a valuable addition. Again, we’re into “ship a native analyser” territory, though, because you have to know which types can be trusted, and which are still unchecked.

Importantly, you can’t rely on static analysis outside of a closed system - a library can’t choose to erase all types, and still make assumptions in code based on users not passing other types.

PHPStan has a specific setting to account for this:

PHPStan by default doesn’t differentiate between PHPDoc and native types. It considers them both as certain.

This might not be what you want in case you’re writing a library whose users might pass a wrong argument type to a function. Setting treatPhpDocTypesAsCertain to false relaxes some of the rules around type-checking.

Replace “PHPDoc” with “erased” and the statement applies to the current discussion. For example:

interface Foo<T: int|string> {
public function bar(): T;
}

function test(Foo<int> $foo): void {
$bar = $foo->bar();
if ( ! is_int($bar) ) {
throw new \TypeError('Expected an int');
}
}

If the generic type can be trusted, then the is_int() check is redundant and can safely be removed. If generics are checked at runtime, it can be trusted; if the final running system has been successfully analysed and proven correct, it can be trusted. But analysing this code in isolation can’t make it trusted; and analyzing the final running system may find cases which can’t be proven until runtime.

As I understand it, the “partial erasure” approach, where the static analyser decides which runtime checks are needed, is used by Dart: https://dart.dev/language/type-system

Rowan Tommins
[IMSoP]

On Tue, 9 Jun 2026 at 10:23, Rob Landers <rob@getswytch.com> wrote:

I don’t have enough time today to write out long responses to both your comments. But to this end, scalar inference requires deciding how to handle at the RFC level, not a POC level.

new Box(42);
new Box(42.0);
new Box("42")

In non-strict mode, are these all equivalent? What exactly is T’s type here?

Scalars in PHP … are weird. I wasn’t going to try to define it here.

— Rob

Handling of this really does not have to be defined at the RFC level. It’s a typechecker implementation — and it’s ok for them to differ.

This is a good post describing how the direct equivalent in Python is treated differently by Python type checkers:
https://pyrefly.org/blog/container-inference-comparison/.

Best wishes,

Matt

On Sun, 10 May 2026 at 20:02, Seifeddine Gmati <azjezz@carthage.software> wrote:

Hello Internals,

I'd like to start the discussion on a new RFC adding bound-erased
generics types to PHP.

Generic type parameters can be declared on classes, interfaces,
traits, functions, methods, closures, and arrow functions, with
bounds, defaults, and variance markers. Type parameters erase to their
bound at runtime; the pre-erasure form is preserved for Reflection and
consumed by static analyzers.

- RFC: PHP: rfc:bound_erased_generic_types
- Implementation: PHP RFC: Bound-Erased Generic Types by azjezz · Pull Request #21969 · php/php-src · GitHub

Thanks,
Seifeddine.

Hello internals,

This is the Intent to Vote message for the Bound-Erased Generic Types RFC.

The discussion thread opened on May 10. The RFC text has been stable
for the last 14 days, no further Major or Minor changes are pending,
and the recent activity in the thread has been around adjacent topics
rather than feedback requiring changes to the RFC itself. I plan to
open voting on 2026-06-14.

If any new substantive feedback comes in during that window, I'll
treat it the same as feedback raised earlier in the thread, and any
Major or Minor change to the RFC text will reset the cooldown and
cancel this Intent to Vote, per policy.

The vote will run for 14 days and will be announced in a separate
[VOTE] thread once it opens. There will be two questions on the RFC:

1. Primary, 2/3 majority required: accept the RFC as a whole.
2. Secondary, simple majority, conditional on the primary passing:
variance marker syntax, `+T` / `-T` (Hack, Scala) vs `in T` / `out T`
(C#, Kotlin). Semantically identical; only the surface syntax differs.

RFC: PHP: rfc:bound_erased_generic_types
Implementation: PHP RFC: Bound-Erased Generic Types by azjezz · Pull Request #21969 · php/php-src · GitHub

Thanks to everyone who engaged with this over the past month.

Regards,
Seifeddine.

On 13.05.26 23:26, Seifeddine Gmati wrote:

- It would be great to be able to see how the userland samples from Laravel, Doctrine, etc... would look when using this new syntax, maybe you could add a new section with this

Agreed. I'll add a parallel section showing what each example looks
like in the proposed native syntax, so readers can see the migration
directly.

Cheers,
Seifeddine.

Hey Seifeddine,

could you please point me to where the parallel sections for the examples were added?

Thanks,
Nick

On Thu, Jun 11, 2026, at 12:27 PM, Seifeddine Gmati wrote:

Hello internals,

This is the Intent to Vote message for the Bound-Erased Generic Types RFC.

The discussion thread opened on May 10. The RFC text has been stable
for the last 14 days, no further Major or Minor changes are pending,
and the recent activity in the thread has been around adjacent topics
rather than feedback requiring changes to the RFC itself. I plan to
open voting on 2026-06-14.

If any new substantive feedback comes in during that window, I'll
treat it the same as feedback raised earlier in the thread, and any
Major or Minor change to the RFC text will reset the cooldown and
cancel this Intent to Vote, per policy.

The vote will run for 14 days and will be announced in a separate
[VOTE] thread once it opens. There will be two questions on the RFC:

1. Primary, 2/3 majority required: accept the RFC as a whole.
2. Secondary, simple majority, conditional on the primary passing:
variance marker syntax, `+T` / `-T` (Hack, Scala) vs `in T` / `out T`
(C#, Kotlin). Semantically identical; only the surface syntax differs.

RFC: PHP: rfc:bound_erased_generic_types
Implementation: PHP RFC: Bound-Erased Generic Types by azjezz · Pull Request #21969 · php/php-src · GitHub

Thanks to everyone who engaged with this over the past month.

Regards,
Seifeddine.

I know we've discussed this in chat, but I want to put it out to the list.

i strongly urge you to hold off on a vote. Generics would be the biggest PHP feature in *years*, and would generate a lot of very positive buzz, both within the community and externally. OTOH, generics getting voted down (for whatever reason) would be... very bad press, both within the community and externally.

I don't feel like Rob's additions have been adequately investigated and evaluated, nor is there a clear consensus on what would be "good enough" for it to be acceptable. That is a problem that should be addressed directly.

Additionally, Rob has noted that the current RFC does allow for code that would be broken and change behavior should generics become enforced in the future. That is a landmine we do not want. (And I reiterate, "if you care, use an SA tool" is an insufficient answer.)

We basically have three options:

1. Erased generics, with all the limitations and problems that implies (basically Seif's current RFC).
2. Monomorphized generics, with the performance hit that implies (basically Rob's patch, with some further development.)
3. Pass on generics, again.

In my mind, option 3 is the absolute worst option. I can think of at least three core features that would benefit from or require generics to be done properly, so having *something* in core is, in my mind, critical. Plus, as previously noted, "Internals rejects generics, even though basically everyone wants it" is going to be the headline (no matter how (in)accurate you feel that is). The only people that win in that scenario are Node.js fanbois.

However, my read of the current discussion is that partially-erased generics (this RFC) is not going to pass. I've been struggling with it for a while, and still not sure how I'm going to vote. I really want to support it, but the gaps that Rob pointed out (around catch) are a problem. And again, they're worse than just "oh it's the wrong type so you get a type error." It completely changes the error pathway that gets executed.

If partially-erased generics doesn't pass, that will leave us with monomorphized/reified generics. That always runs into the "but performance" problem, yet, there has never been a consensus as to what an acceptable CPU or memory hit would be. Because *there is guaranteed to be one*. If we want generics, then we either accept partial erasure or we accept some performance hit. We're going to have to deal with one or the other, and pretending that some magic free-reified implementation will appear is a fool's errand.

So to anyone who plans to vote against the current RFC, please state what you would consider an acceptable performance hit for going all the way. *We need to agree on that*, so that Rob or anyone else can see if we can hit it. That's the step that hasn't been achieved yet (due in part to our current process having no way to handle that). What Rob has expressed so far frankly seems like an acceptable cost to me already, but I know others disagree. But holding out for zero-cost is simply impractical.

My recommendation, in fact, would be to get one or more additional people involved to help on the performance front, and put forward an equivalent enforced-generics RFC. (Same syntax and semantics as the current one, which are pretty solid aside from the in/out question.) Let's bang on that and get it right, and then we can pass that. Given the timing, I would suggest such an RFC not target 8.6. Instead, we can plan ahead that 8.6 (this year) will be the end of the 8.x series, PHP 2027 will be PHP 9.0, and will include whatever the result of that further generics work is.

That gives us plenty of time to:
1. Ensure it's rock solid, and as performant as we can manage
2. Develop additional features for core that can leverage generics, so when they ship we have stuff already using it. This also helps flush out any edge case bugs before it ships.
3. Gives us a clear path toward a major PR and press win (PHP 9.0, Now with Generics!), which the project desperately needs.

I want to reiterate: "Internals votes down generics" is the absolute worst outcome, for literally everyone who cares about PHP. As we say in Chicago, "you don't call the vote until you know you have the votes." Right now, I don't think we have the votes. We need to take the time and do the work to get this *right*, and ensure not just passage, but broad support and endorsement, and the right tooling built on top of it.

Voting on the current RFC right now will not get us there.

--Larry Garfield

On 6/13/26 14:38, Larry Garfield wrote:

I want to reiterate: "Internals votes down generics" is the absolute worst outcome, for literally everyone who cares about PHP. As we say in Chicago, "you don't call the vote until you know you have the votes." Right now, I don't think we have the votes. We need to take the time and do the work to get this *right*, and ensure not just passage, but broad support and endorsement, and the right tooling built on top of it.

Voting on the current RFC right now will not get us there.

I'd like to second Larry's call for holding off on a vote and getting this right and ready for a PHP 9 release in 2027.

Cheers,
Ben

On 14.06.2026 05:01, Ben Ramsey wrote:

Voting on the current RFC right now will not get us there.

I'd like to second Larry's call for holding off on a vote and getting this right and ready for a PHP 9 release in 2027.

As someone who's gonna vote No on this RFC I concur. Larry's plan makes a lot of sense.

--
Aleksander Machniak
Kolab Groupware Developer [https://kolab.org]
Roundcube Webmail Developer [https://roundcube.net]
----------------------------------------------------
PGP: 19359DC1 # Blog: https://kolabian.wordpress.com

Hello everyone,

as someone purely on the userland side of things, I agree with Larry’s plan. It’s sensible and gives plenty of time to actually work out the kinks.

As to the Erased Generics vs Monomorphized Generics and their performance hit, I honestly do not see why people are hung up on wanting no performance hit. As a userland developer, I expect generics to have a performance hit, and not a minor one. The more powerful the feature, the greater the performance impact. I also expect generics in PHP to do actual runtime type checking the same way the whole type system in PHP already does (I’m a strict type mode user through and through). If generics end up giving a performance hit of 30-40% in the places they are used compared to non-generics code - frankly, considering the benefits of code simplification, maintainability improvements, removing code we don’t need any more and getting a major type safety at runtime - a server or two that might needed to be added is a small price to pay.
We are not talking about overall PHP slowing down 30-40% just because a single generic collection or two are used in the code, right? As i understand, the slowdown is at the boundaries of the generic usage, not across all code. And it has been already said, that there are definite optimizations to be had - the effort just has to be put into it.

Using try/catch incurs a performance hit, which everyone knows, yet we still use it. Because it is worth it.

My 0.02$.

···

Arvīds Godjuks+371 26 851 664
arvids.godjuks@gmail.com
Telegram: @psihius https://t.me/psihius

Voting is now open. The [VOTE] thread is here:

The vote started on 2026-06-14 at 16:50 UTC and ends on 2026-06-28 at 17:00 UTC.

Two votes on the RFC page:

1. Primary, 2/3 majority: accept the RFC.
2. Secondary, simple majority, conditional on the primary: variance
marker syntax.

Thanks again to everyone who contributed to the discussion.

Regards,
Seifeddine.

On Sun, 10 May 2026 at 20:02, Seifeddine Gmati <azjezz@carthage.software> wrote:

Hello Internals,

I'd like to start the discussion on a new RFC adding bound-erased
generics types to PHP.

Generic type parameters can be declared on classes, interfaces,
traits, functions, methods, closures, and arrow functions, with
bounds, defaults, and variance markers. Type parameters erase to their
bound at runtime; the pre-erasure form is preserved for Reflection and
consumed by static analyzers.

- RFC: PHP: rfc:bound_erased_generic_types
- Implementation: PHP RFC: Bound-Erased Generic Types by azjezz · Pull Request #21969 · php/php-src · GitHub

Thanks,
Seifeddine.