- Date: 2017-12-05
- Author: Levi Morrison [email protected]
- Proposed for: PHP 7.NEXT or 8.0
- Status: Draft
Taking parameters by references allows functions to modify variables and those modifications are viewable in the sender. This comes at a price: the variable must be moved to the heap to be reference counted. Additionally, the type of the argument is checked on function entry but not on function exit. Interested parties can return by reference and add a return type but this does not scale beyond one parameter. This RFC proposes out
and inout
parameters which solve these issues while providing additional safety.
The out
and inout
modifiers can be added to function parameters. A parameter with an out
or inout
modifier cannot also take the parameter by-reference. The out
or inout
modifier is placed before the type declaration if one exists. When a parameter is out
or inout
then at the call site the argument must be preceded with an ampersand &
. Here is the class swap
function definition and its usage:
function swap(inout $a, inout $b): void {
$c = $a;
$a = $b;
$b = $c;
}
$x = 1;
$y = 2;
swap(&$x, &$y);
// $x is now 2, $y is now 1
// Neither $x nor $y is a reference
For simple variables a reference will be avoided. When passing an array member or object property it will unfortunatley require a reference.
All out parameters are required to be initialized before the function returns. The proposed strategy is to initialize a temporary variable to be undefined via IS_UNDEF
which is used in the function body instead of the out parameter. When the function returns we check if it is still undefined. If it is we will error; if it is not then we move it to the out parameter and perform any necessary type checks. Out parameters are not type-checked at the beginning of function call.
Here is an example of where the out parameter was never initialized:
function initialize(out int $x): void {}
initialize(&$a);
// Uncaught TypeError: out parameter $x was never assigned as value
If the engine will permit it without too much trouble then this RFC proposes that reads of uninitialized out parameters become fatal errors or exceptions.
function initialize(out int $x): void {
echo $x;
$x = 1;
}
initialize(&$a);
// Fatal error: out parameter $x was read before initialization
When an inout
modifier is used with a type declaration the parameter type is checked when the function begins and when the function returns.
Here is an example demonstrating the type check when the function begins:
function swap_ints(inout int $a, inout int $b): void {
$c = $a;
$a = $b;
$b = $c;
}
$x = 1;
$y = new StdClass();
swap_ints(&$x, &$y);
// Uncaught TypeError: Argument 2 passed to swap_int() must be of the type integer, object given
Here is an example demonstrating the type check when the function returns:
function assign_zero_if_negative(inout int $a): void {
if ($a < 0) {
// mistake is here
$a = new stdclass();
}
}
$x = -1;
assign_zero_if_negative(&$x);
// Uncaught TypeError: out parameter $a must be of the type integer, object assigned
TODO: better wording for the error?
The "try pattern" in C# commonly uses out parameters. Here is a far-too-simple Optional
class with such a method:
final class Optional {
private $hasData;
private $data;
private function __construct(bool $hasData, $data) {
$this->hashData = $hasData;
$this->data = $data;
}
function TryGetValue(out $result): bool {
$result = $data;
return $hasData;
}
static function of($data): self {
return new self(true, $data);
}
static function none(): self {
return new self(false, $data);
}
}
$optional = Optional::of(1);
if ($optional->TryGetValue(&$data)) {
// do what you will with $data
} else {
// there wasn't a value so the current value of $data is meaningless
}
If array_push
was written with this proposal then it might look like this:
function array_push(inout array $stack, ... $values): int {
foreach ($values as $value) {
$stack[] = $value;
}
return \count($stack);
}
The out
and inout
strings may be need to be keywords. New keywords are a backwards compatibility. There are very few places they would be permitted so we may be able to avoid making the full keywords.
The vote will be a simple "yes" or "no" for the question "Accept this proposal as outlined?". Two-thirds of the votes must be "yes" to be accepted.
This proposal targets 7.NEXT if the strings out
and inout
are not keywords and targets 8.0 otherwise.