Re: Re: [VOTE] let construct (Block Scoping)
| From: | Larry Garfield | Date: | Sun, 25 Jan 2026 18:40:12 +0000 |
| Subject: | Re: Re: [VOTE] let construct (Block Scoping) | ||
| References: | 1 2 3 4 | Groups: | php.internals |
| Request: | Send a blank email to internals+get-129919@lists.php.net to get a copy of this message | ||
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