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

Hi

we just opened the vote on the “let construct (Block Scoping)” RFC (which was originally proposed as “use construct”, but the keyword changed as a result of the discussion).

Please find the following resources:

- RFC: PHP: rfc:optin_block_scoping
- Official Discussion Thread: php.internals: [RFC][Discussion] use construct (Block Scoping)
- Related Discussion Thread: php.internals: Examples comparing Block Scoped RAII and Context Managers

There is one primary vote to cast on the RFC. Voting will run for 2 weeks until 2026-02-05 09:30 UTC.

Best regards
Tim Düsterhus

On 22/01/2026 09:12, Tim Düsterhus wrote:

Hi

we just opened the vote on the “let construct (Block Scoping)” RFC (which was originally proposed as “use construct”, but the keyword changed as a result of the discussion).

I have voted No.

I really want block scoping as a feature in PHP, but I do not want this syntax.

For those who didn't follow the discussion thread, I will try to summarise my reasons:

- I don't think PHP is special enough to break with a strong tradition here. A huge family of languages use roughly the same syntax for variable declarations, even though they have very different details of how they work. That includes C and Java, but also JS and Perl, and even VisualBasic (all ultimately traceable to ALGOL).

- Users are likely to be coming from those related languages, particularly JS, and are likely to be confused by how this syntax works.

- For instance, they would not expect a comma-separated list to be equivalent to a set of nested blocks, which affects the behaviour of cases like let($a=new Foo, $a=new Bar)

- The use cases for combining with "if" or "foreach" are interesting, but the result is confusing - the important control flow keyword ends up in the middle of the line.

- For other use cases, requiring an extra block adds noise. For instance, if we ever add auto-capture closures, a JS-style a concise block-scoped declaration would be very useful to avoid accidental capture.

PHP itself already has "ALGOL-style" declarations, for "const", "global", and "static". I think adding a "let" or "var" keyword with the same syntax would be more consistent, and more useful.

There would be details to work out, but dozens of other languages to learn from.

My thanks to Seifeddine and Tim for their work on this, and to Tim in particular for patient and respectful discussion.

--
Rowan Tommins
[IMSoP]

On Thu, Jan 22, 2026, 12:12 Rowan Tommins [IMSoP] <imsop.php@rwec.co.uk> wrote:

On 22/01/2026 09:12, Tim Düsterhus wrote:

Hi

we just opened the vote on the “let construct (Block Scoping)” RFC
(which was originally proposed as “use construct”, but the keyword
changed as a result of the discussion).

I have voted No.

I really want block scoping as a feature in PHP, but I do not want this
syntax.

For those who didn’t follow the discussion thread, I will try to
summarise my reasons:

  • I don’t think PHP is special enough to break with a strong tradition
    here. A huge family of languages use roughly the same syntax for
    variable declarations, even though they have very different details of
    how they work. That includes C and Java, but also JS and Perl, and even
    VisualBasic (all ultimately traceable to ALGOL).

  • Users are likely to be coming from those related languages,
    particularly JS, and are likely to be confused by how this syntax works.

  • For instance, they would not expect a comma-separated list to be
    equivalent to a set of nested blocks, which affects the behaviour of
    cases like let($a=new Foo, $a=new Bar)

  • The use cases for combining with “if” or “foreach” are interesting,
    but the result is confusing - the important control flow keyword ends up
    in the middle of the line.

  • For other use cases, requiring an extra block adds noise. For
    instance, if we ever add auto-capture closures, a JS-style a concise
    block-scoped declaration would be very useful to avoid accidental capture.

PHP itself already has “ALGOL-style” declarations, for “const”,
“global”, and “static”. I think adding a “let” or “var” keyword with the
same syntax would be more consistent, and more useful.

There would be details to work out, but dozens of other languages to
learn from.

My thanks to Seifeddine and Tim for their work on this, and to Tim in
particular for patient and respectful discussion.


Rowan Tommins
[IMSoP]

I agree, the proposed syntax and nested block hell are terrible!

The traditional syntax with implicit and invisible nested block are much better, many languages and years of trusting proved that.

On Thu, Jan 22, 2026 at 10:17 AM Tim Düsterhus <tim@bastelstu.be> wrote:

Hi

we just opened the vote on the “let construct (Block Scoping)” RFC
(which was originally proposed as “use construct”, but the keyword
changed as a result of the discussion).

Hi,

Thank you for your work on this.

I voted no because the RFC is presented as a way to dispose of
resources immediately (quoting: "This feature provides immense value,
particularly for modern applications built on long-lived servers,
where disciplined and immediate resource cleanup is not just a best
practice, but a necessity for stability and performance."), yet it
doesn't in practice as it relies on reference counting as a proxy for
cleaning up at block exit.

While reference counting is deterministic, ensuring that a value is
uniquely referenced before leaving a block is impossible, and cannot
be enforced by the block itself. In practice, useful code will send
the resource to other functions or 3rd party code that may retain a
reference to it. Moreover, there are many non-obvious ways in which a
reference count can be increased or a value’s lifetime extended:
exceptions or backtraces may capture arguments, fibers, generators, or
closures with non obvious lifetimes retain their local variables,
closures tend to create cycles, foreach variables are not always
unset, etc.

Therefore there is a non-zero possibility that a let() statement will
not in fact cleanup the resource, despite the stated goal. Fixing this
later would be a BC break as people may rely on this behavior.

The RFC refers to similar constructs from other languages, but none of
them rely on reference counting for this purpose. I'm not aware of
languages relying on reference counting for this.

Best Regards,
Arnaud

Am 22.01.2026, 18:57:04 schrieb Arnaud Le Blanc <arnaud.lb@gmail.com>:

On Thu, Jan 22, 2026 at 10:17 AM Tim Düsterhus <tim@bastelstu.be> wrote:

Hi

we just opened the vote on the “let construct (Block Scoping)” RFC

(which was originally proposed as “use construct”, but the keyword

changed as a result of the discussion).

Hi,

Thank you for your work on this.

I voted no because the RFC is presented as a way to dispose of
resources immediately (quoting: “This feature provides immense value,
particularly for modern applications built on long-lived servers,
where disciplined and immediate resource cleanup is not just a best
practice, but a necessity for stability and performance.”), yet it
doesn’t in practice as it relies on reference counting as a proxy for
cleaning up at block exit.

I agree that relying on reference counting can bite you, but I still voted Yes, because

  • We only have reference counting to rely on in PHP as far as memory goes, and thats what people writing cleanup critical code rely on already. This gives a much nicer syntax.

  • The future scope lists improvements as next steps, like the error when RC > 1 or Disposable interfaces, this is a first step towards something that is missing dearly.

While reference counting is deterministic, ensuring that a value is
uniquely referenced before leaving a block is impossible, and cannot
be enforced by the block itself. In practice, useful code will send
the resource to other functions or 3rd party code that may retain a
reference to it. Moreover, there are many non-obvious ways in which a
reference count can be increased or a value’s lifetime extended:
exceptions or backtraces may capture arguments, fibers, generators, or
closures with non obvious lifetimes retain their local variables,
closures tend to create cycles, foreach variables are not always
unset, etc.

Therefore there is a non-zero possibility that a let() statement will
not in fact cleanup the resource, despite the stated goal. Fixing this
later would be a BC break as people may rely on this behavior.

The RFC refers to similar constructs from other languages, but none of
them rely on reference counting for this purpose. I’m not aware of
languages relying on reference counting for this.

Best Regards,
Arnaud

Hi,

On Fri, Jan 23, 2026 at 10:59 AM Benjamin Außenhofer
<kontakt@beberlei.de> wrote:

I agree that relying on reference counting can bite you, but I still voted Yes, because

We only have reference counting to rely on in PHP as far as memory goes

For disposal, only if we decide so. The RFC proposes to introduce
block scoping, and could have made the decision to deterministically
dispose of values as they go out of scope.

The RFC refers to similar features in Python and Hack, with the same
basic syntax, both of which use reference counting as primary garbage
collection mechanism, yet disposal is deterministic in this context.

and thats what people writing cleanup critical code rely on already. This gives a much nicer syntax.

In my experience, relying on this causes occasional bugs that are
unusually difficult to address (and to avoid in the first place).

The future scope lists improvements as next steps, like the error when RC > 1 or Disposable interfaces, this is a first step towards something that is missing dearly.

Unfortunately these improvements won't make it because they will be
breaking changes.

Best Regards,
Arnaud

Hi

Reordering the quoted parts to improve the structure of the reply.

Am 2026-01-24 15:13, schrieb Arnaud Le Blanc:

and thats what people writing cleanup critical code rely on already. This gives a much nicer syntax.

In my experience, relying on this causes occasional bugs that are
unusually difficult to address (and to avoid in the first place).

The RFC lists 4 possible use cases within the example section:

a) Example showing an edge case (solving the foreach reference bug)

Here the goal is not to destruct the underlying value, but to free up the variable name for reuse.

b) Example showing the combination of let and if()

The same applies here. The goal is to prevent accidental use of a variable after it is no longer meant to be used. The stored value will not have special destruction logic attached.

c) Example showing memory-efficient batch processing

For this one the concern is "memory consumption". To my understanding you considered this a safe use case for reference counting in php.internals: Re: [RFC][Discussion] use construct (Block Scoping).

d) Example showing reliable resource management

This leaves the locking example, where the object guards a non-memory resource that might accidentally be captured. In my experience developing PHP, I never had an issue with accidental escapes of resource objects and I feel that keeping them in a local variable is sufficiently robust to prevent them from accidentally being captured.

I acknowledge that your experience was different than mine, that's why the RFC lists possible improvements to make this easier/safer as future scope.

The RFC proposes a generically applicable language feature that solves example use cases (a), (b), (c) perfectly fine as-is. It is no worse than existing solutions for (d) and leaves a clear path for future scope ideas to support (d) even better. I feel that reducing it to just the "lock example" / "external resource management" is not doing the RFC justice.

The future scope lists improvements as next steps, like the error when RC > 1 or Disposable interfaces, this is a first step towards something that is missing dearly.

Unfortunately these improvements won't make it because they will be
breaking changes.

This is false. We would not list anything as "Future Scope" when it would be a breaking change. Both the RC > 1 and the Disposable interface would require the author of the resource class to add an interface for the corresponding functionality to become active and would therefore be fully opt-in. Adding new symbols (incl. interfaces) to PHP is not considered a breaking change as per our policy: policies/release-process.rst at main · php/policies · GitHub.

Throwing for every RC > 1 object at the end of the `let()` block would break the "let ($user = $repository->find(1)) if ($user !== null)" use case if the repository internally uses an identity map and thus keeps a reference to all objects it hands out. Any special `Disposable` handling for a user object would also break the identity map, because it now stores an object that already was disposed and thus likely is in an invalid state.

For disposal, only if we decide so. The RFC proposes to introduce
block scoping, and could have made the decision to deterministically
dispose of values as they go out of scope.

See above: This would possibly break at least one of the presented use cases of the RFC.

The RFC refers to similar features in Python and Hack, with the same
basic syntax, both of which use reference counting as primary garbage
collection mechanism, yet disposal is deterministic in this context.

The "References" section is non-normative with regard to the proposal itself and just supplies supplementary information. My previous email provides further context to the listed references: php.internals: Re: [RFC][Discussion] use construct (Block Scoping).

Best regards
Tim Düsterhus

Hi,

Le sam. 24 janv. 2026 à 18:35, Tim Düsterhus <tim@bastelstu.be> a écrit :

Am 2026-01-24 15:13, schrieb Arnaud Le Blanc:

and thats what people writing cleanup critical code rely on already.
This gives a much nicer syntax.

In my experience, relying on this causes occasional bugs that are
unusually difficult to address (and to avoid in the first place).

The RFC lists 4 possible use cases within the example section:

I agree that the RFC adresses 3 of these use cases, but I regret that the pitfall in the last one is being dismissed or minimized, while claiming to address that use case. There is enough evidence in other languages that the pitfall is real and common, as features claiming to address the use case are designed explicitly to avoid it.

This leaves the locking example

This is a simple one, but most code will send the scoped resource to other functions to perform anything useful, increasing the risk of capture.

The future scope lists improvements as next steps, like the error when

RC > 1 or Disposable interfaces, this is a first step towards
something that is missing dearly.

Unfortunately these improvements won’t make it because they will be
breaking changes.

This is false. We would not list anything as “Future Scope” when it

would be a breaking change. Both the RC > 1 and the Disposable interface
would require the author of the resource class to add an interface for
the corresponding functionality to become active and would therefore be
fully opt-in. Adding new symbols (incl. interfaces) to PHP is not
considered a breaking change as per our policy:
https://github.com/php/policies/blob/main/release-process.rst#backward-compatibility.

The issue is that implementing Disposable on existing objects after the fact is a BC break as by then some code may rely on the fact that disposal is not enforced. This precludes making any exiting object (or streams) Disposable if that interface is introduced later, because this might break people’s code. Making it opt-in in the let() syntax is a potential solution to that, but it’s error prone.

For disposal, only if we decide so. The RFC proposes to introduce
block scoping, and could have made the decision to deterministically
dispose of values as they go out of scope.

See above: This would possibly break at least one of the presented use
cases of the RFC.

As you said this would be opted in by implementing Disposable.

The RFC refers to similar features in Python and Hack, with the same
basic syntax, both of which use reference counting as primary garbage
collection mechanism, yet disposal is deterministic in this context.

The “References” section is non-normative with regard to the proposal
itself and just supplies supplementary information. My previous email
provides further context to the listed references:
https://news-web.php.net/php.internals/129202.

I know, but making comparisons is natural as the RFC claims to address the same use cases. Based on the previous thread it feels like the design was adapted to borrow principles from Rust (objects can be disposed only when unreachable), but PHP doesn’t have features that would make this reliable, like explicit lifetimes. I think that the way that languages closer to PHP implement this use case would fit PHP better.

Best Regards,
Arnaud

Hey Tim,

I went for “abstain”.

I’m a proponent of functional programming paradigms, and I frequently touch Haskell / Nix, which have let ... in ... and ... where ... , which are nice.

I feel like cluttering the PHP language for something that is already (in my own opinion) resolved by IIFEs (and inlining/optimization thereof) is not necessary.

I don’t mind if the feature makes it into the language, but I feel like it just complicates the AST further, for very marginal gains.

Greets,

···

Marco Pivetta

https://mastodon.social/@ocramius

https://ocramius.github.io/

Le 22/01/2026 à 10:12, Tim Düsterhus a écrit :

Hi

we just opened the vote on the “let construct (Block Scoping)” RFC (which was originally proposed as “use construct”, but the keyword changed as a result of the discussion).

Please find the following resources:

- RFC: PHP: rfc:optin_block_scoping
- Official Discussion Thread: php.internals: [RFC][Discussion] use construct (Block Scoping)
- Related Discussion Thread: php.internals: Examples comparing Block Scoped RAII and Context Managers

Hello,

I personally can't vote, but I'd like very much this feature to happen. I have no strong opinion about the syntax (let is fine for me, as would be with, using, or anything else...).

Just wanted to add my 2cents here, thank you all contribs to the time you give to PHP.

--

Pierre

Hey Marco,

···

On 27.1.2026 13:45:06, Marco Pivetta wrote:

Hey Tim,

I went for “abstain”.

I’m a proponent of functional programming paradigms, and I frequently touch Haskell / Nix, which have let ... in ... and ... where ... , which are nice.

I feel like cluttering the PHP language for something that is already (in my own opinion) resolved by IIFEs (and inlining/optimization thereof) is not necessary.

I don’t mind if the feature makes it into the language, but I feel like it just complicates the AST further, for very marginal gains.

Greets,

Marco Pivetta

That’s pretty much the sentiment I share. It’s additional complexity for the language, with a benefit not worth it.

I definitely prefer this over context managers (though they don’t solve exactly the same goals, there’s quite a bit of overlap).

But for me I’d like to see neither.

As to the more specific semantics of the RFC:

It’s not natural to write code this way (especially if there are multiple such objects created across a function - you don’t want to nest 5 times, for 5 objects), and there are only very few instances where it actually matters when exactly a value is destroyed.

While the examples certainly correctly show usages where it’s useful, an explicit unset(); just solves this as well in most cases. However a let() is much more implicit than the actual unset() telling me “I NEED this to be cleared here”. It also doesn’t prevent me from outliving the value.
It feels like let is supposed to be clearing the object. However, with your example for process_file() it’s fine. However if you have zend.exception_ignore_args=0 (as you should if you disable displaying errors) and forward the $file object as an arg (e.g. read_all($file)), the $file object will be attached to the exception (being an arg). It will not, as one may expect, be actually freed when the exception is thrown.
That’s subtle, and let() isn’t the holy grail for solving these things. It creates false expectations in sufficiently complex code.

The main remaining utility I see in let() is highlighting intentions, rather than being actually useful in the language. Which goes back to the first point of “additional complexity, but not worth it”.

Thanks,

Bob

Hi

Am 2026-01-22 10:12, schrieb Tim Düsterhus:

There is one primary vote to cast on the RFC. Voting will run for 2 weeks until 2026-02-05 09:30 UTC.

Voting now closed. The RFC was declined with 13 (Yes) to 15 (No) and 5 Abstentions (46%).

Best regards
Tim Düsterhus