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]

Reply via email to