Hi, I'm Khaled Alam, a software engineer based in the UAE and a long-time
PHP user. I'd like to request RFC karma to publish an RFC proposing a native
"defer" feature and gather community feedback.
Inspired by Zig's "defer", it schedules a block to run when the current
function exits (return, exception, or natural end), executed in LIFO order,
aimed at safer cleanup without repetitive try/finally scaffolding.
Proof-of-concept implementation PR
https://github.com/php/php-src/pull/20786.
Current status:
[x] Parser/AST support for defer { ... }
[x] Compilation + VM support with per-call defer stack (not shared on
op_array)
[x] Defers run correctly on explicit/implicit returns, LIFO order
[_] I’m currently working on exception-unwinding handling so defers also run
when an exception is thrown
Wiki username:
khaledalamxyzGitHub username*: *khaledalam
Thank you for considering granting RFC karma.
Best regards,
Khaled Alam
Hi Khaled
I'd like to request RFC karma to publish an RFC proposing a native "defer" feature and gather community feedback.
RFC karma was granted. Good luck!
Inspired by Zig's "defer", it schedules a block to run when the current function exits (return, exception, or natural end), executed in LIFO order, aimed at safer cleanup without repetitive try/finally scaffolding.
I've already shared some technical feedback on the PR, let me reiterate here.
I think it's important that defers are executed on scope-end, rather
than function-end. For functions with many loop iterations, this would
otherwise schedule tons of defer executions for resources that could
have long been released. Relatedly, the way this is currently
implemented, defer referencing changing variables is ineffective. For
example:
function test() {
for ($i = 0; $i < 10; $i++) {
$resource = allocate_some_resource();
defer { close($resource); }
}
}
is roughly equivalent to:
function test() {
for ($i = 0; $i < 10; $i++) {
$resource = allocate_some_resource();
}
for ($i = 0; $i < 10; $i++) {
close($resource);
}
}
Which is obviously incorrect, closing the same resource over and over
again. Executing at end-of-scope would mostly resolve this issue.
On a more technical level, the implementation mostly mirrors
FAST_CALL/FAST_RET used for finally. This seems unnecessary.
FAST_CALL/FAST_RET also already handle exceptions, which your
implementation is currently missing. I believe defer { <code> } <code until end of scope> could mostly be sugar for try { <code until end of scope> } finally { <code> }.
Given those things are so similar, the RFC should make it clear why a
new syntax is beneficial. I suppose it puts the alloc/free code
lexically closer together, which might be worth it for some people.
Ilija
Inspired by Zig's "defer", it schedules a block to run when the current function exits (return, exception, or natural end), executed in LIFO order, aimed at safer cleanup without repetitive try/finally scaffolding.
Oh, and what I forgot to mention here (but not in the PR), there are
two recent RFCs that try to solve a very related problem.
Ilija