Re: Examples comparing Block Scoped RAII and Context Managers

From: Date: Wed, 26 Nov 2025 18:08:11 +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-129461@lists.php.net to get a copy of this message
On Wed, Nov 19, 2025, at 4:19 PM, Rowan Tommins [IMSoP] wrote: > On 18/11/2025 17:23, Larry Garfield wrote: >> One thing I definitely do not like is the need for a >> FileWrapper class in the RAII file-handle example. That seems like >> an unnecessary level of abstraction just to squeeze the fclose() >> value onto the file handle. The fully-separated Context Manager seems a more flexible approach. > > > Yes, exploring how exactly that flexibility could be used was part of > my motivation for the examples I picked. > > The downside is that it is slightly harder to understand at first > glance: someone reading "using (file_for_write('file.txt') as $fh)" > might well assume that $fh is the value returned from > "file_for_write('file.txt')", rather than the value returned from > "file_for_write('file.txt')->enterContext()". > > What made sense to me was comparing to an Iterator that only goes > around once - in "foreach (files_to_write_to() as $fh)", the > "files_to_write_to()" call doesn't return $fh either, > "files_to_write_to()->current()" does. That's a good analogy, I like it. >> I also noted that all of the examples wrap the context block (of whichever syntax) in a >> try-catch of its own. I don't know if that's going to be a common pattern or not. If so, >> might it suggest that the using block have its own built-in optional >> catch and finally for one-off >> additional handling? That could point toward the Java approach of merging this functionality into >> try, but I am concerned about the implications of making both t� >> uݳ,˜åÛp–+4catch and finally effectively optional >> on try blocks. I am open to discussion on this front. (Anyone know >> what the typical use cases are in Python?) > > > Looking at the parser, I realised that a "try" block with neither > "catch" nor "finally" actually matches the grammar; it is only rejected > by a specific check when compiling the AST to opcodes. Without that > check, it would just compile to some unnecessary jump table entries. > > > > I guess an alternative would be allowing any statement after the > using() rather than always a block, as in Seifeddine and Tim's > proposal, which allows you to stack like this: > > using ($db->transactionScope()) try { > // ... > } > catch ( SomeSpecificException $e ) { > // ... > } > > Or, the specific combination "try using( ... )" could be added to the > parser. (At the moment, "try" must always be followed by "{".) > > > > As I noted in one of the examples > (file-handle/application/1b-raii-with-scope-block.php), there is a > subtle difference in semantics between different nesting orders - with > "try using()", you can catch exceptions thrown by enterContext() and > exitContext(); with "using() try", you can catch exceptions before > exitContext() sees them and cleans up. > > It seems Java's try-with-resources is equivalent to "try using()": > >> In a try-with-resources statement, any catch or finally block is run after the resources >> declared have been closed. Thanks. I'll discuss these options with Arnaud. Anyone else want to weigh in here? >> Which is exactly the benefit of the separation of the Context Manager from the Context >> Variable. The CM can be written to rely on unset() closing the >> object (risk 2), or to handle closing it itself (risk 1), as the developer determines. > > > Something the examples I picked don't really showcase is that a Context > Manager doesn't need to be specialised to a particular task at all, it > can generically implement one of these strategies. > > The general pattern is this: > > class GeneralPurposeCM implements ContextManager { > public function __construct(private object $contextVar) {} > public function enterContext(): object { return $this->contextVar; } > public functoin exitContext(): void {} > } > > - On its own, that makes "using(new GeneralPurposeCM(new Something) as > $foo) { ... }" a very over-engineered version of "{ let $foo = new > Something; ... }" True! It may make sense eventually to provide a "UnsetThis(mixed $var)" CM in the stdlib. Not something to include now, but I've no issue with it existing eventually. > Incidentally, while checking I had the right method name in the above, > I noticed the Context Manager RFC has an example using "leaveContext" > instead, presumably an editing error. :) Indeed. Fixed now, thanks. --Larry Garfield

« previous php.internals (#129461) next »