Hi

Am 2025-12-10 00:19, schrieb Rowan Tommins [IMSoP]:
The drawback I see is that on a longer block, you have to come up with a name for that unused variable, and make sure you don't accidentally unset or overwrite it.


Apparently Java's workaround for that is to allow "unnamed variables", which have the expected lifetime, but can't be accessed: https://openjdk.org/jeps/456

try-with-resources is given as one of the example use cases ("var" infers the type; "_" in place of the name makes it an "unnamed variable"):

try (var _ = ScopedContext.acquire()) {
    ... no use of acquired resource ...
}

Notably, this is *not* the same meaning for "_" as, say, C#, where "_ = foo()" tells the compiler to discard the return value of foo(): https://learn.microsoft.com/en-us/dotnet/csharp/fundamentals/functional/discards

Something that came to my mind would be that the semantics of `let()` already allow for the following to guarantee that a value stays alive for the entire block even if accidentally reassigned:

    class Scoped {
        public function __construct() { echo __METHOD__, PHP_EOL; }
        public function __destruct() { echo __METHOD__, PHP_EOL; }
    }

    echo "Before scope", PHP_EOL;
    let (
        $scoped = new Scoped(),
        $scoped,
    ) {
        echo "Start of scope", PHP_EOL;

        $scoped = 'something else';

        echo "End of scope", PHP_EOL;
    }
    echo "After scope", PHP_EOL;

which outputs:

    Before scope
    Scoped::__construct
    Start of scope
    End of scope
    Scoped::__destruct
    After scope

It is definitely on the more obscure end, but I believe it is reasonably easy to understand *why* it works when seeing that it does work. This semantics of redeclaring/shadowing variables within the same (not a nested!) scope is something that folks might also know from Rust to ensure that a temporary input stays alive for long enough without taking up a valuable variable name:

// https://play.rust-lang.org/?version=stable&mode=debug&edition=2024&gist=e5c84849b529aab5c1a5033ddfde2870
    fn get_bar() -> String {
        return "bar".to_owned();
    }

    fn id(v: &str) -> &str {
        return v;
    }

    fn main() {
        let bar = id(get_bar().as_str());

        println!("{:?}", bar);
    }

This is not currently valid, as the String returned from `get_bar()` is destroyed at the semicolon, but a reference to it is still stored in `bar`. The following however would work:

    let bar = get_bar();
    let bar = id(bar.as_str());

    println!("{:?}", bar);

Best regards
Tim Düsterhus

Reply via email to