Re: Examples comparing Block Scoped RAII and Context Managers

From: Date: Sun, 16 Nov 2025 22:32:54 +0000
Subject: Re: Examples comparing Block Scoped RAII and Context Managers
References: 1  Groups: php.internals 
Request: Send a blank email to internals+get-129277@lists.php.net to get a copy of this message
On 16/11/2025 15:29, Frederik Bosch wrote:
On Sat, 15 Nov 2025 23:11:44 +0000, Rowan Tommins [IMSoP] wrote:
The Block Scoping RFC and the Context Manager RFC cover a lot of similar use cases, and a lot of the discussion on both threads has been explicitly comparing them. To try to picture better how they compare, I have put together a set of examples that implement the same code using both features, as well as some other variations, in this git repo: https://gitlab.com/imsop/raii-vs-cm
Another suggestion would be to follow the Java try-with-resources syntax. It does not require a new keyword to be introduced, as with the Context Manager syntax. Moreover, it aligns with current try-catch-finally usage already implemented by PHP developers. try ($transaction = $db->newTransaction()) {
    $db->execute('UPDATE tbl SET cell = :cell', ['cell'=>'value']);
}
I did have a look into that when somebody mentioned it earlier, and I believe it is almost exactly equivalent to C#'s "using" statement: - C#: keyword "using", interface "IDisposable", method "Dispose": https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/statements/using - Java: keyword "try", interface "AutoCloseable", method "close": https://docs.oracle.com/javase/tutorial/essential/exceptions/tryResourceClose.html The main difference I've spotted is how it combines with other blocks. In Java, you can use the same block as both try-with-resources and try-catch: try ( Something foo = new Something ) {
    blah(foo);
} catch ( SomeException e ) {
    whatever();
} In C#, you can instead use a statement version of using within any existing block: try {
    using ( Something foo = new Something );
    blah(foo);
} catch ( SomeException e ) {
    whatever();
} Both are very similar to RAII, but because both languages use non-immediate garbage collection, the method is separate from the normal destructor / finalizer, and other references to the "closed"/"disposed" object may exist. At the moment, I haven't included examples inspired by these, because I thought they would be too similar to the existing RAII examples and clutter the repo. But if there's a difference someone thinks is worth highlighting, I can add one in.
Any object that implements TryWithContext can be used with such syntax. The function returns the exit context operation as callback. interface TryWithContext {    public function tryWith(): \Closure; }
I can't find any reference to this in relation to Java; did you take it from a different language, or is it your own invention? Either way, it looks like an interesting variation on the Python-based enterContext/exitContext. Do you have any thoughts on what it's advantages or disadvantages would be?
Rather auto-capture I'd suggest explicit complete scope capture, by using the use keyword without parenthesis.
This is a completely separate discussion I was hoping not to get into. Although I've personally advocated for "function() use (*) {}" in the past, I've used "fn() {}" in the "auto-capture" examples because it is the syntax most often proposed. It's irrelevant for this example anyway, so I've edited it out below.
For a transaction it might look like this. class Transaction implements TryWithContext {
    public function tryWith(): \Closure
    {
        $this->db->beginTransaction();
        return function (?\Throwable $e = null) {
            if ($e) {
                $this->db->rollbackTransaction();
                return;
            }
            $this->db->commitTransaction();
        };
    }
}
Looking at this example, it feels like it loses the simplicity of RAII without gaining the power of Context Managers. In particular, tryWith() as shown can't return a separate value to be used in the loop, like beginContext() can in the Python-inspired proposal. The class would have a separate constructor and tryWith() method, with no clear distinction. Making the cleanup function anonymous prevents the user directly calling dispose()/close()/__destruct() out of sequence; but it doesn't stop the object being used after cleanup, which seems like a more likely source of errors. Still, it's interesting to explore these variations to see what we can learn, so thanks for the suggestion. -- Rowan Tommins [IMSoP]

Thread (8 messages)

« previous php.internals (#129277) next »