On 27/11/2025 08:34, Tim Düsterhus wrote:

Hi Tim,

As promised, I eventually got back to this e-mail properly, and have updated the examples in response.

For convenience, here's the link again: https://gitlab.com/imsop/raii-vs-cm




The block scoping RFC has been updated to `let()`.


Updated.


1.

For db-transaction/implementation/1-raii-object.php I'd like to note that it is not necessary to proxy execute() through the transaction object. It could also be used as a simple guard object which only purpose is to be constructed and destructed.


That's true. The "wikimedia-at-ease" example illustrates that style, because there aren't any methods we'd want there at all.

The drawback I see is that on a longer block, you have to come up with a name for that unused variable, and make sure you don't accidentally unset or overwrite it.


Apparently Java's workaround for that is to allow "unnamed variables", which have the expected lifetime, but can't be accessed: https://openjdk.org/jeps/456

try-with-resources is given as one of the example use cases ("var" infers the type; "_" in place of the name makes it an "unnamed variable"):

try (var _ = ScopedContext.acquire()) {
    ... no use of acquired resource ...
}

Notably, this is *not* the same meaning for "_" as, say, C#, where "_ = foo()" tells the compiler to discard the return value of foo(): https://learn.microsoft.com/en-us/dotnet/csharp/fundamentals/functional/discards


In the case of a transaction, it feels logical for at least the commit() and rollback() methods to be on the Transaction class, but other methods would be a matter of style. I've removed execute() to take the simplest route.



2.

The RAII object in 'file-handle' serves no purpose. PHP will already call `fclose()` when the resource itself goes out of scope, so this is existing behavior with extra steps. The same is true for the 0-linear-code example in file-object. You don't need the `fclose()` there.


This is true as long as nothing stores an additional reference to the file handle. Having a separate "guard object" doesn't fully prevent this - a reference to the guard object itself could leak - but does make it less likely.

It also gives somewhere to customise the open and close behaviour, such as adding locking, or converting false-on-error to an exception (more on that below).


To see what it looks like, I've added a "file-handle-unguarded" scenario, which exposes the handle much more directly: it doesn't force fclose(), and leaves the application to handle the failure of fopen()

The CM example benefits from being able to "break" out of the using{} block; as far as I can see, the current RFC doesn't allow that from a let{} block, is that correct?




3.

The docblock in locked-pdf/application/2-context-manager.php is incorrectly copy and pasted.


Well spotted. Fixed.


I was about to comment that the 'file' examples were not equivalent, because the context manager and IOC ones didn't include the error logging, until I noticed it was hidden away in the implementation. This probably suggests that I implicitly expected to see all relevant control flow. Exception handling and logging in particular probably greatly depend on the surrounding context to be useful (e.g. to adapt the log message or to enhance it with additional context data). So while it might superficially look cleaner / simpler, I feel that this kind of generic handling will bite you sooner or later. So with the context manager example, I would expect the “exception introspection” capability to be used to properly tear down the context, but not for cross-cutting concerns such as logging.


That's a reasonable comment. Looking at those examples more closely, I realise I actually messed up the error handling in most of them - they never check for the false returned by fopen()

I've moved the logging out into the "application", but updated all the "implementations" to throw a "FileOpeningException" if fopen() returns false.

This really demonstrates the value of the "try using() { ... }" short-hand Larry & Arnaud have added to the CM RFC - we need the try to be on the *outside* of the block to catch the new exception. I presume a similar "try let() { ... }" could be added to yours?


It's interesting how similar the example end up for simple scenarios, but I think if we had block-scoped variables, I'd still like Context Managers for some of the more complex cases.


--
Rowan Tommins
[IMSoP]

Reply via email to