Skip to content

Instantly share code, notes, and snippets.

@morrisonlevi
Last active December 6, 2017 18:35
Show Gist options
  • Save morrisonlevi/95ec4c67cb0d4fd0c9547d0a262010af to your computer and use it in GitHub Desktop.
Save morrisonlevi/95ec4c67cb0d4fd0c9547d0a262010af to your computer and use it in GitHub Desktop.
Preliminary draft for out and inout parameters

Out and inout parameters

  • Date: 2017-12-05
  • Author: Levi Morrison levim@php.net
  • Proposed for: PHP 7.NEXT or 8.0
  • Status: Draft

Introduction

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.

Description

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.

Out parameters

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

Inout parameters

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?

Examples

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);
}

Backward Incompatible Changes

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.

Voting

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment