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 catch 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


Thread (8 messages)

« previous php.internals (#129461) next »