Re: [PHP-DEV] Re: [VOTE] let construct (Block Scoping)

Hi

Am 2026-01-23 00:29, schrieb Joseph Leedy:

While I appreciate all of the hard work that Seifeddine and you put into this RFC, Tim, if I had the power to vote, I would vote "No" on this RFC as I like the approach laid out in the Context Manager RFC much better.. Thank you both!

I'd like to note that while the two RFCs share some of the scope, neither is a direct replacement of the other. I think Rowan previously summarized it well in this email: php.internals: Re: [RFC] Context Managers.

The focus of the “let construct (Block Scoping)” RFC is enabling block scoping - as the name implies. The examples in the “Examples” section of the RFC are intended to reflect some real-world situations where either Seifeddine or I would've liked to have block scoping. Except for the “process_file()” example, I don't believe the Context Manager RFC (as currently proposed) would be a suitable replacement.

Best regards
Tim Düsterhus

On Fri, Jan 23, 2026, at 2:20 AM, Tim Düsterhus wrote:

Hi

Am 2026-01-23 00:29, schrieb Joseph Leedy:

While I appreciate all of the hard work that Seifeddine and you put
into this RFC, Tim, if I had the power to vote, I would vote "No" on
this RFC as I like the approach laid out in the Context Manager RFC
much better.. Thank you both!

I'd like to note that while the two RFCs share some of the scope,
neither is a direct replacement of the other. I think Rowan previously
summarized it well in this email:
php.internals: Re: [RFC] Context Managers.

As currently written, yes. However, as noted previously in the Context Manager thread we are open to allowing arbitrary values in the CM position, which would fall back to just using that value and then unsetting it. That would effectively replace the `let` syntax entirely with a unified syntax.

I have also voted No on this RFC, for many of the reasons already stated:

* The functionality is too limited.
* The functionality is inherently unreliable due to value escape.
* The syntax is far too non-standard and clumsy.
* Expanding CMs to fully encompass the behavior of this RFC is trivial, which would avoid redundant and confusing syntax.

--Larry Garfield

On Fri, Jan 23, 2026, at 01:20, Tim Düsterhus wrote:

I'd like to note that while the two RFCs share some of the scope,
neither is a direct replacement of the other. I think Rowan previously
summarized it well in this email:
php.internals: Re: [RFC] Context Managers.

The focus of the “let construct (Block Scoping)” RFC is enabling block
scoping - as the name implies. The examples in the “Examples” section of
the RFC are intended to reflect some real-world situations where either
Seifeddine or I would've liked to have block scoping. Except for the
“process_file()” example, I don't believe the Context Manager RFC (as
currently proposed) would be a suitable replacement.

Best regards
Tim Düsterhus

My apologies for the confusion. Thank you for the clarification, Tim. I retract my previous statement.

Hi

Am 2026-01-23 16:47, schrieb Larry Garfield:

As currently written, yes. However, as noted previously in the Context Manager thread we are open to allowing arbitrary values in the CM position, which would fall back to just using that value and then unsetting it. That would effectively replace the `let` syntax entirely with a unified syntax.

I have also voted No on this RFC, for many of the reasons already stated:

* The functionality is too limited.
* The functionality is inherently unreliable due to value escape.

For these two I refer to the email I just sent in reply to Arnaud. I believe that 4 example use cases are by no means an indication of an RFC being limited in usefulness. Block scoping is a well-established feature in programming languages and I'm positive that folks will be able to come up with even more examples of where block scoping would have been helpful to them to avoid bugs / make their code easier to reason about.

* The syntax is far too non-standard and clumsy.

My understanding the initial paragraph of your email is that the entire functionality of the block scoping RFC could be included in the Context Manager RFC. The Context Manager RFC uses the same "high-level" syntax of having a dedicated block for declarations - just using `using` instead of `let` as the keyword and using `=>` instead of `=` for an assignment (the latter of which was considered confusing in the discussion). I'm sure I must be misunderstanding, because I don't follow how the syntax of this RFC is "non-standard and clumsy" without this equally applying to the context manager RFC. Can you rephrase?

* Expanding CMs to fully encompass the behavior of this RFC is trivial, which would avoid redundant and confusing syntax.

The same is true the other way around, possible ideas are listed in the Future Scope.

I'd also like to note that it was an explicit design goal of the block scoping RFC to compose nicely with existing control structures to be generically applicable. This enables several of the example use cases listed in the RFC. The context manager RFC requires braces and a proposed special case for `try` instead of just accepting any statement like block scoping does. Due to its composibility, the block scoping RFC is even compatible with future additions to the language (provided the future additions follow the established syntax). As an example, pattern matching comes to mind as another way of "writing into variables". Block scoping will just work with match:

     let ($x, $y) $result = match ($p) is {
         Point(:$z, :$x, y: 4) => "x is $x, y is 4, z is $z",
     };

to prevent $x and $y from leaking out of the `match()`. And similarly to the `foreach()` future scope, we could allow `match ($p) let is` to automatically block scope all variables bound by the pattern.

Best regards
Tim Düsterhus

On Sat, Jan 24, 2026, at 12:16 PM, Tim Düsterhus wrote:

My understanding the initial paragraph of your email is that the entire
functionality of the block scoping RFC could be included in the Context
Manager RFC. The Context Manager RFC uses the same "high-level" syntax
of having a dedicated block for declarations - just using `using`
instead of `let` as the keyword and using `=>` instead of `=` for an
assignment (the latter of which was considered confusing in the
discussion). I'm sure I must be misunderstanding, because I don't follow
how the syntax of this RFC is "non-standard and clumsy" without this
equally applying to the context manager RFC. Can you rephrase?

Block scoping and CMs are related and overlap, but are different things.

`let`, in every language I am aware of that uses it, is an inline part of the variable declaration, and then they do something to resolve the "dead zone" question. Languages that have block scoping have had it from the beginning, so "variable dies at the next }" is built into the language semantics and development culture. Neither is true in PHP. So having a block-esque syntax for "only these special blocks and these special variables get block scoping" is something I have never seen before, and feels very clumsy.

CMs are about packaging up setup/teardown logic to make them reusable, and therefore more ergonomic. That setup/teardown can and often does include unsetting variables, but that's not its main purpose. It's just an implementation detail. In every language with such functionality, it is a block level construct.

Block scoping as presented here is essentially a "junior, limited CM", with a future scope of adding a Disposable interface to make it a less-limited CM. I fundamentally believe that is the wrong way around, and have already discussed elsewhere (I think) why forcing the context manager (packaged logic) and context variable (the variable(s) that will need cleanup) to be the same value is fundamentally limiting and flawed.

I proposed allowing the `using` syntax to handle non-CM objects specifically to incorporate the "degenerate case" where unsetting is the only meaningful teardown, and thereby avoid the need for a boilerplate new JustUnsetTheThing($thing) for the degenerate cases. CMs can also return $this from enterContext() if appropriate, which would then have the same effect as the Disposable interface.

So no matter how you slice it, having a separate CM from context variable provides more flexibility than the proposed `let` syntax, in a consistent and unified fashion. By tweaking CMs, we can allow it to also cover the simple, degenerate cases that block scoping handles in addition to the more robust options it naturally supports.

I'd also like to note that it was an explicit design goal of the block
scoping RFC to compose nicely with existing control structures to be
generically applicable. This enables several of the example use cases
listed in the RFC. The context manager RFC requires braces and a
proposed special case for `try` instead of just accepting any statement
like block scoping does. Due to its composibility, the block scoping RFC
is even compatible with future additions to the language (provided the
future additions follow the established syntax). As an example, pattern
matching comes to mind as another way of "writing into variables". Block
scoping will just work with match:

     let ($x, $y) $result = match ($p) is {
         Point(:$z, :$x, y: 4) => "x is $x, y is 4, z is $z",
     };

to prevent $x and $y from leaking out of the `match()`. And similarly to
the `foreach()` future scope, we could allow `match ($p) let is` to
automatically block scope all variables bound by the pattern.

thinking_face.gif

This is sort of what we were trying to noodle through with the questions about expression-oriented `using`, but it didn't stick. I would have to talk to Arnaud, but I suppose it's probably possible to allow `using` to work on an arbitrary construct and inherit its block. I'm quite open to discussing the feasibility of that.

It's really unfortunate that both proposals were worked on separately, as I do believe there are good ideas in both. But I would much rather work from the more-flexible design and then short-cut the degenerate cases, as that gives, I believe, a better outcome in the end.

--Larry Garfield