rfc:exit-as-function

PHP RFC: Transform exit() from a language construct into a standard function

Introduction

The exit language construct (and its alias die) can be used on its own without parentheses 1) to terminate a PHP script with status code 0, or it can be used like a “function” which accepts an optional argument $status that can either be an integer, in which case the PHP script will be terminated with the given integer as the status code, or it can be a string, in which case the PHP script is terminated with status code 0 and the string is printed to STDOUT.

However, because exit() is not a proper function it cannot be called with a named argument, passed to functions as a callable, does not respect the strict_types declare, and most confusingly, it does not follow the usual type juggling semantics.

Indeed, any value which is not an integer, is cast to a string. This means passing an array or a resource to exit() will not throw a TypeError but print Array or Resource id #%d respectively with the relevant warning being emitted. However, it does throw a TypeError for non-Stringable objects.

Furthermore, arguments of type bool are cast to string instead of int, violating the standard type juggling semantics for a string|int union type. This is something that we find especially confusing for CLI scripts which may have a boolean $has_error variable passed to exit() with the assumption that false will be coerced to 0 and true coerced to 1.

Since PHP 8.0 exit() doesn't use the bailout mechanism anymore, but throws a special kind of exception which cannot be caught, and which does not execute finally blocks. 2)

Finally, there didn't seem to ever have been a necessity for exit() to be its own dedicated opcode.

Proposal

We propose to make exit() a proper function with the following signature:

function exit(string|int $status = 0): never {}

Parsing of the keywords remains unchanged, but instead of being compiled to a ZEND_EXIT opcode they are compiled to a function call.

Therefore, it will remain impossible to declare functions named exit or die in namespaces, or to disable/remove them via the disable_functions INI directive.

Backward Incompatible Changes

The impact of this RFC is deemed to be low.

The behaviour of values of different types passed to exit() will be altered to match the usual type juggling semantics:

Argument passed Current behaviour New behaviour Consequences
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
int int int No change, interpreted as exit code
string string string No change, interpreted as status message
bool string int Was status message, now exit code
float string int Was status message, now exit code, with a possible "Implicit conversion from float to int loses precision" deprecation notice
null string int Was status message, now exit code, with "Passing null to parameter #1 ($status) of type string|int is deprecated" deprecation notice
stringable object string string No change, interpreted as status message
non-stringable object TypeError TypeError None
array string TypeError Was status message with warning, now TypeError
resource string TypeError Was status message with warning, now TypeError

Future scope

These are ideas for future proposals that are not part of this RFC:

  • Deprecate using exit without parentheses
  • Execute finally blocks when exit is called
  • Allow disabling exit()/die() functions via the disable_functions INI directive, similarly to how it is possible to disable assert()

Version

Next minor version, PHP 8.4.

Vote

As per the voting RFC a yes/no vote with a 2/3 majority is needed for this proposal to be accepted.

Voting started on 2024-07-30 and will end on 2024-08-13.

Accept Transform exit() from a language construct into a standard function RFC?
Real name Yes No
alcaeus (alcaeus)  
ashnazg (ashnazg)  
brzuchal (brzuchal)  
bukka (bukka)  
bwoebi (bwoebi)  
cmb (cmb)  
cpriest (cpriest)  
cschneid (cschneid)  
dams (dams)  
davey (davey)  
devnexen (devnexen)  
dharman (dharman)  
ericmann (ericmann)  
galvao (galvao)  
girgias (girgias)  
heiglandreas (heiglandreas)  
jimw (jimw)  
john (john)  
kalle (kalle)  
kguest (kguest)  
kocsismate (kocsismate)  
levim (levim)  
mauricio (mauricio)  
mbeccati (mbeccati)  
nicolasgrekas (nicolasgrekas)  
nielsdos (nielsdos)  
ocramius (ocramius)  
petk (petk)  
ramsey (ramsey)  
santiagolizardo (santiagolizardo)  
sergey (sergey)  
theodorejb (theodorejb)  
timwolla (timwolla)  
villfa (villfa)  
weierophinney (weierophinney)  
zimt (zimt)  
Final result: 24 12
This poll has been closed.

Changelog

In a prior version of this RFC the T_EXIT token was removed.

Notes

1)
which is somewhat akin to using a constant as it can be used in expressions: https://3v4l.org/sL9Q5
rfc/exit-as-function.txt · Last modified: 2024/08/25 12:21 by cmb