Re: Examples comparing Block Scoped RAII and Context Managers

From: Date: Wed, 17 Dec 2025 19:59:54 +0000
Subject: Re: Examples comparing Block Scoped RAII and Context Managers
References: 1 2 3  Groups: php.internals 
Request: Send a blank email to internals+get-129643@lists.php.net to get a copy of this message
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

« previous php.internals (#129643) next »