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]

Reply via email to