Re: Examples comparing Block Scoped RAII and Context Managers
| From: | Tim Düsterhus | 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()) {Something that came to my mind would be that the semantics of... 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
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