A Go release has Go developers arguing about what Go even is, and I'll admit I'm enjoying the view from our side of the fence. Go 1.27 brings generic methods, a rewritten JSON engine slid underneath the old API, UUIDs in the standard library, experimental portable SIMD, post-quantum ML-DSA signatures, cheaper allocation paths for objects under 80 bytes, and runtime detection of goroutines that will never wake up again. Cue the anxiety: has the language that built its brand on saying no finally started saying yes? I think the anxiety is aimed at the wrong target. Simplicity was never a property of the spec sheet. It lives in the diff your colleague reviews on a Tuesday afternoon, and by that measure a language can grow enormously complicated internals while staying simple where it counts. PHP figured this out years ago, mostly by accident, and Go 1.27 reads to me like a formal admission that we were onto something.
Look at the JSON change first, because it's the most PHP-shaped move in the whole release. Go now has encoding/json/v2 with stricter defaults and more configuration, but the old encoding/json keeps working because it now runs on top of the new machinery, with an opt-out if the transition breaks something. Old import, new engine. We have lived through exactly this. PHP 7 replaced the guts of the Zend Engine, reworked zvals and hashtables, and most codebases upgraded by changing a Docker tag. PHP 8.0 bolted a JIT onto opcache and your controllers neither knew nor cared. Nobody stood up at a conference and accused PHP of betraying minimalism, because nobody ever mistook PHP's engine for a minimal artifact. The deal was always that core does the ugly work so userland stays boring, and boring userland is the entire point.
Generic methods are the harder case, and I want to be fair to the people who are nervous. A method that can introduce its own type parameters, independent of its receiver, is not engine plumbing. It shows up in code review. It makes Result types with a Map that transforms into a differently parameterized Result feel like normal code, and once that feels normal, fluent typed pipelines follow, then builder APIs, then library designs imported from Rust and Kotlin. The strongest argument against expressive features has never been that they're useless. It's that they change what your teammates reach for by default, and a team's default style is much harder to review than any single clever function. That argument deserves respect. I've watched a Laravel codebase where one enthusiastic macro author quietly redefined what idiomatic meant for everyone else on the team.
And still I land on the side of the additions, partly because of how Go fenced them. Interface methods can't declare their own type parameters in 1.27, and generic methods can't satisfy interface contracts, which means the new power exists but can't propagate through the language's main abstraction channel. That's rationing, and rationing works. PHP runs a version of the same experiment daily: we have generics in practice through docblock annotations and PHPStan or Psalm enforcing them, with zero runtime support. It's awkward, it's a truce rather than a design, but the predicted collapse into abstraction soup never arrived. Teams that wanted typed collections got them. Teams that didn't kept writing arrays. The tooling carried the complexity so the engine didn't have to, which is just another answer to the same question of who holds the bag.
The stdlib UUID story is where the two ecosystems genuinely diverge, and I'm less sure who's right. Go pulled UUIDs into the standard library and then had to give database/sql special awareness of the new type so it plays nicely with database conversions, a wart that cuts against the small-interfaces ethos. PHP went the other way entirely: core never shipped UUIDs, and ramsey/uuid plus symfony/uid became de facto standards through Composer. Our approach keeps core clean and pushes the choice onto every project. Their approach blesses one implementation and accepts an architectural smudge to make it ergonomic. I've typed composer require ramsey/uuid so many times that my fingers do it unsupervised, and I still can't tell you whether that's freedom or just a tax I've stopped noticing.
What I'd push back on is the slippery-slope framing, the idea that a language which learns to absorb complexity will keep absorbing until it becomes the thing it fled. Accumulation is real, sure. But the guardrail isn't refusal, it's a legible rule about where complexity is allowed to live. PHP's implicit rule has held up better than we give it credit for: spend freely below the surface, in opcache and the JIT and preloading and the FFI layer, and be stingy with syntax that changes what average code looks like. Enums, readonly properties, first-class callable syntax, all of those cleared a high bar and earned their place. The RFC process is slow and occasionally maddening, and it's also why PHP 8.4 code still reads like PHP to someone who left the ecosystem in 2016.
So here's what I'd actually ask you, because the Go debate is a rehearsal for one we keep postponing. If an RFC landed tomorrow offering real reified generics in PHP, engine-enforced, no docblocks, at the cost of serious internal complexity and the near certainty that every major library would sprout typed builder and pipeline APIs within two years, would you vote yes? Or is the current truce, static analysis doing generics' job while the runtime stays out of it, secretly the best version of complexity placement we could have designed on purpose? I go back and forth weekly. Tell me where you land, and tell me what your codebase would look like three years after either answer.
Comments
No comments yet — be the first.
Open the discussion
No account or password needed — just enter your e-mail and we’ll send you a one-time sign-in link. First time here? You’re set up automatically.
Your rating will be applied automatically after you sign in.
Check your inbox
We’ve sent a sign-in link to …. Open it on this device — this tab will sign you in automatically.
Nothing arrived? Check your spam folder — and mark the mail as "Not spam" so it lands in your inbox next time.