On 09/11/2025 16:52, Tim Düsterhus wrote:
We don't think so. Our goal with this proposal is - as the title of
the RFC suggests - making block scoping / limiting lifetimes of
variables more convenient to use. The goal is to make it easier for
developers and static analysis tools to reason about the code, for
example because variables are less likely to implicitly change their
type.
Perhaps part of the problem is the comparisons the RFC calls on. It
mentions features from three existing languages, none of which is about
block scoping; and doesn't mention the ways other languages *do*
indicate block scoping.
In C#, all local variables must be declared before use, and are scoped
to the current block; the "using" keyword doesn't change that. Instead,
it is for a very specific purpose: correctly "disposing" what .net calls
"unmanaged resources" - normally, raw memory pointers passed out from
some non-.net library or OS call. While objects can have "finalizers"
which work very like destructors, .net only guarantees that they will be
run "eventually", when the object is garbage collected. The IDisposable
interface, and the "using" keyword which makes use of it, exist to
release the unmanaged resources at a deterministic moment.
The Hack "using" statement is clearly inspired by C#, but takes it
further: there are no destructors or finalizers, so the *only* way to
perform automatic cleanup is implementing the IDisposable interface. The
compiler then enforces various restrictions on that class: it can only
be created as part of a "using" statement, and can only be used in such
a way that it will be unreferenced after disposal. Unlike C#, Hack's
local variables are normally function-scoped, so it is significant that
disposal happens at the end of a block; but it is neither intended or
usable as a general-purpose block scoping mechanism.
The Python "with" statement is completely different. In the words of the
PEP that specifies it, it is "to make it possible to factor out standard
uses of try/finally statements". Like PHP and Hack, Python's local
variables are function-scoped; but the with statement doesn't change
that - if a variable is initialised in the "with" statement, it is still
available in the rest of the function, just like the target of "as" in a
PHP "foreach". The "with" statement isn't actually concerned with the
lifetime of that variable, and in fact can be used without one at all;
its purpose is to call "enter" and "exit" callbacks around a block of code.
If what you're interested in is a general-purpose block scope, none of
these are relevant examples. The most relevant that comes to my mind is
JavaScript's "let", which is an opt-in block scope added to an exsting
dynamic language. A comparison of that approach to what you propose here
would be interesting.
Consider this example:
function foo() {
$a = 1;
var_dump($a);
{
var_dump($a);
let $a = 2;
var_dump($a);
}
var_dump($a);
}
What would you expect the output of each of the `var_dump()`s to be?
Yes, this is a problem that any language with block-scoping has to
tackle. Since there are many languages which have that feature, I'm sure
plenty has been written on the pros and cons of different approaches.
I'm not aware of any language that requires a specific kind of block
in order to introduce a new scope, but that doesn't mean it's a bad
idea. The approaches I am aware of are:
- Shadowing: At the point of declaration, any other variable with the
same name becomes inaccessible, but not over-written. Result: 1, 1, 2, 1
- Forbidden shadowing: At the point of declaration, if there is another
variable of the same name in scope, an error occurs. Result: 1, 1, Error
(let statement must not shadow $a from outer scope)
- Hoisting: The variable declaration can occur anywhere in the scope,
and affects the whole scope even lines above it. Used by JavaScript's
"var" keyword, and very confusing. Result: 1, 2, 2, 1
- Forbidden hoisting: The variable declaration can occur anywhere in the
scope, but lines above that point are forbidden from accessing it. Used
by JavaScript's "let" keyword, with the dramatic name "Temporal Dead
Zone". Result: 1, Error (must not access block variable $a before
declaration)
--
Rowan Tommins
[IMSoP]