Re: Examples comparing Block Scoped RAII and Context Managers

From: Date: Sun, 16 Nov 2025 15:29:35 +0000
Subject: Re: Examples comparing Block Scoped RAII and Context Managers
Groups: php.internals 
Request: Send a blank email to internals+get-129271@lists.php.net to get a copy of this message
On Sat, 15 Nov 2025 23:11:44 +0000, Rowan Tommins [IMSoP] wrote:
Hi all, 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 A few notes: - The syntax for the two proposals is based on the current RFC text. If they are updated, e.g. to use different keywords, I will update the examples. - I have included examples with closures which automatically capture by value, since a lot of the same use cases come up when discussing those. - There are many scenarios which could be included, and many ways each example could be written. I have chosen scenarios to illustrate certain strengths and weaknesses, but tried to fairly represent a "good" use of each feature. However, I welcome feedback about unintentional bias in my choices. - Corrections and additional examples are welcome as Merge Requests to the repo, or replies here. With that out of the way, here are my own initial thoughts from working through the examples: - RAII + block scope is most convenient when protecting an existing object which can be edited or extended. - When protecting a final object, or a native resource, RAII is harder to implement. In these cases, the separation of Context Manager from managed value is powerful. - Context Managers are very concise for safely setting and resetting global state. RAII can achieve this, but feels less natural. - An "inversion of control" approach (passing in a callback with the body of the protected block) requires capturing all variables *not* scoped to the block. Even with automatic by-value capture, those needed *after* the block would need to be listed for capture by reference. - Building a Context Manager from a Generator can lead to very readable code in some cases, and closely mimics an "inversion of control" approach without the same variable capture problems. I would be interested in other people's thoughts. Regards, -- Rowan Tommins [IMSoP]
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']);
} 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; } For a transaction it might look like this. Rather auto-capture I'd suggest explicit complete scope capture, by using the use keyword without parenthesis. class Transaction implements TryWithContext {
    public function tryWith(): \Closure
    {
        $this->db->beginTransaction();
        return function (?\Throwable $e = null) use {
            if ($e) {
                $this->db->rollbackTransaction();
                return;
            }
            $this->db->commitTransaction();
        };
    }
}

« previous php.internals (#129271) next »