rfc:explicit_send_by_ref

Differences

This shows you the differences between two versions of the page.

Link to this comparison view

Both sides previous revisionPrevious revision
Next revision
Previous revision
rfc:explicit_send_by_ref [2019/07/25 10:57] – Phrasing nikicrfc:explicit_send_by_ref [2022/01/25 18:21] (current) – Move to inactive ilutov
Line 1: Line 1:
-====== PHP RFC: Explicit call-site pass-by-reference ======+====== PHP RFC: Allow explicit call-site pass-by-reference annotation ======
   * Date: 2017-12-02   * Date: 2017-12-02
   * Author: Nikita Popov <nikic@php.net>   * Author: Nikita Popov <nikic@php.net>
-  * Proposed for: PHP 7.3+  * Proposed for: PHP 8.0
   * Implementation: https://github.com/php/php-src/pull/2958   * Implementation: https://github.com/php/php-src/pull/2958
-  * Status: Under Discussion+  * Status: Inactive
  
 ===== Introduction ===== ===== Introduction =====
  
-By-reference parameters are currently only declared when defining a function, while the call-site does not distinguish between by-value and by-reference passing. This RFC proposes to allow marking by-reference arguments at the call-site as well.+By-reference parameters are currently only declared when defining a function, while the call-site does not distinguish between by-value and by-reference passing. This RFC proposes to allow marking by-reference arguments at the call-site as well, and make this required at a future time.
  
 Consider the following example of an ''inc()'' function, which accepts a number by reference and increments it: Consider the following example of an ''inc()'' function, which accepts a number by reference and increments it:
Line 44: Line 44:
 The requirement that the reference is marked at both the definition- and call-site makes this feature different from the call-site pass-by-reference that was used in PHP 4. The requirement that the reference is marked at both the definition- and call-site makes this feature different from the call-site pass-by-reference that was used in PHP 4.
  
-Of courseit remains possible to not explicitly mark reference-passing at the call-site. We may wish to deprecate and remove this ability in the future, but this RFC does not propose this.+By default, the use of a call-site annotation will remain optionalHowever, a mode that makes it required may be introduced depending on the outcome of the [[https://github.com/php/php-rfcs/pull/2|language evolution RFC]].
  
 ===== Motivation ===== ===== Motivation =====
  
-The motivation for this proposal boils down to making it simpler to understand code, both for programmers and for static analyzers.+The motivation for this proposal boils down to making it simpler to understand code: For programmers, for static analyzers and for the PHP optimizer and executor.
  
 As a simple example, consider the following two calls, which look deceptively similar: As a simple example, consider the following two calls, which look deceptively similar:
Line 97: Line 97:
 Does this code use an uninitialized variable ''$x''? Unless it's possible to infer the type of ''$obj'' and determine which method is being called and whether it uses by-reference passing, it's impossible to answer this question. In all likelihood the method uses by-value passing and this is a programming error, but it might also be an intentional use of the by-reference automatic initialization. Does this code use an uninitialized variable ''$x''? Unless it's possible to infer the type of ''$obj'' and determine which method is being called and whether it uses by-reference passing, it's impossible to answer this question. In all likelihood the method uses by-value passing and this is a programming error, but it might also be an intentional use of the by-reference automatic initialization.
  
-While static analyzers in IDEs and CI systems will make reasonable assumptions in this case, the PHP implementation itself does not have this luxury. In fact, our inability to determine at compile-time whether a certain argument is passed by-value or by-reference is one of the most significant obstacles in our ability to analyze and optimize compiled code. The runtime dispatch between by-value and by-reference passing also adds significant complexity to the Zend VM, with at least 7 opcodes dedicated to only this task+While static analyzers in IDEs and CI systems will make reasonable assumptions in this case, the PHP implementation itself does not have this luxury. In fact, our inability to determine at compile-time whether a certain argument is passed by-value or by-reference is one of the most significant obstacles in our ability to analyze and optimize compiled code. The runtime dispatch between by-value and by-reference passing also adds significant complexity to the Zend VM, with at 9 (!!) opcodes dedicated to this task.
- +
-This proposal does not resolve this issue, because it introduces an entirely optional feature. The previous syntax where pass-by-reference is not explicitly marked will continue to work. We may want to deprecate and remove it in the future, but this RFC does not propose this, as it is a quite significant change.+
  
 ===== Detailed proposal ===== ===== Detailed proposal =====
Line 150: Line 148:
  
 Apart from these additional error checks, the call-site reference-passing annotation does not affect execution semantics in any way. Apart from these additional error checks, the call-site reference-passing annotation does not affect execution semantics in any way.
 +
 +==== Mode with required call-site annotations ====
 +
 +This section depends on the outcome of the [[https://github.com/php/php-rfcs/pull/2|language evolution RFC]]. If we decide to introduce editions, then this section will only apply in the next edition. If we decide to introduce fine-grained declares, then the changes only apply if the ''require_explicit_send_by_ref'' declare is enabled. If we decide to not pursue a mechanism for opt-in backwards-incompatible changes, then this section becomes void, and the question of making call-site annotations required can be reconsidered at a later time. As a placeholder, we will assume the declare-based approach here.
 +
 +While optionally annotating by-reference passing already helps readability, we can only reap the full benefits outlined in the motivation section, if the use of call-site annotations is required. If the ''require_explicit_send_by_ref'' declare is enabled, then all by-reference passes must be annotated with ''&'' at the call-site, otherwise an exception will be thrown:
 +
 +<code php>
 +declare(require_explicit_send_by_ref=1);
 +
 +function inc(&$num) { $num++; }
 +
 +$i = 0;
 +inc($i);
 +// Uncaught Error: Cannot pass parameter 1 by reference
 +</code>
 +
 +If the argument is a prefer-ref argument of an internal function, then adding the ''&'' annotation will pass it by reference, while not adding it will pass it by value. Outside this mode, the passing behavior would instead be determined by the VM kind of the argument operand.
 +
 +Just like ''strict_types'', the ''require_explicit_send_by_ref'' option only affects call-sites inside the file. Whether the function was declared in a file with ''require_explicit_send_by_ref'' enabled or not does not matter, only the used mode at the call-site matters.
 +
 +==== Forwarding references in __call, call_user_func and similar ====
 +
 +The ''%%__call()%%'' and ''%%__callStatic()%%'' magic methods can be used to forward calls, but this does not work properly with by-reference arguments:
 +
 +<code php>
 +class Incrementor {
 +    public function inc(&$i) {
 +        $i++;
 +    }
 +}
 +
 +class ForwardCalls {
 +    private $object;
 +    public function __construct($object) {
 +        $this->object = $object;
 +    }
 +    public function __call(string $method, array $args) {
 +        return $this->object->$method(...$args);
 +    }
 +}
 +
 +$forward = new ForwardCalls(new Incrementor);
 +$i = 0;
 +$forward->inc($i);
 +var_dump($i); // int(0)
 +</code>
 +
 +The above code silently "works", but the variable ''$i'' will not actually get passed by reference. A by-reference pass does occur at the ''%%$this->object->$method(...$args)%%'' call, but at this point the value stored in ''$args'' and the variable ''$i'' have no relation to each other.
 +
 +The explicit call-site annotation can be used to allow passing the variable by reference:
 +
 +<code php>
 +$forward = new ForwardCalls(new Incrementor);
 +$i = 0;
 +$forward->inc(&$i);
 +var_dump($i); // int(1)
 +</code>
 +
 +This will make the corresponding element in ''$args'' be a reference, which the argument unpack then passes on to the called function.
 +
 +Similarly, ''call_user_func()'' can now be used to call functions that accept by-reference parameters:
 +
 +<code php>
 +function inc(&$i) { $i++; }
 +
 +$i = 0;
 +call_user_func('inc', $i);
 +// Warning: inc() expects argument #1 ($i) to be passed by reference, value given
 +var_dump($i); // int(0)
 +
 +$i = 0;
 +call_user_func('inc', &$i);
 +var_dump($i); // int(1)
 +</code>
 +
 +Both of these features are achieved by introducing a new internal argument passing mode ''prefer-val''. Just like ''prefer-ref'' it accepts both by-value and by-reference, but prefers by-value passing unless by-reference passing is forced by a call-site annotation.
 +
 +==== Differences to the removed "call-time pass-by-reference" feature ====
 +
 +In PHP 4 (and available via deprecated ''allow_call_time_pass_reference'' option until PHP 5.4), by-reference passing was performed by adding an annotation **only** at the call-site, but not the declaration-site:
 +
 +<code php>
 +function inc($num) { $num++; }
 +
 +$i = 0;
 +inc(&$i);
 +</code>
 +
 +This was very problematic, because the argument of any function could be forced into being a reference by adding a call-site annotation.
 +
 +PHP 5 moved towards specifying by-reference passing at the declaration-site, making by-value/by-reference passing part of the API contract of the function, as it should be.
 +
 +This proposal differs from both in that it requires the by-reference annotation at **both** the declaration-site and the call-site. It is extremely unfortunate that this hasn't been done when the original migration of the by-reference passing system happened, as it could have avoided a lot of unnecessary code churn while arriving at a better system. Given this past mistake, the next best thing we can do is address it now.
  
 ===== Backward Incompatible Changes ===== ===== Backward Incompatible Changes =====
  
-None. This use of this feature is optional.+None. The use of this feature is optional, and the backwards-incompatible part is opt-in.
  
 ===== Other languages ===== ===== Other languages =====
Line 169: Line 261:
 With the notable exception of C++ (which most likely inspired our current reference-passing syntax), reference annotations are required both at the declaration and the call-site. With the notable exception of C++ (which most likely inspired our current reference-passing syntax), reference annotations are required both at the declaration and the call-site.
  
-In languages where references are first-class types (rather than a special feature of calls specifically), it is possible to store the obtained reference in a variable. In this case the reference may not be obtained at a point prior to the call-site. However, the reference has to be explicitly obtained at //some// point.+In languages where references are first-class types (rather than a special feature of calls specifically), it is possible to store the obtained reference in a variable. In this case the reference may be obtained at a point prior to the call-site. However, the reference has to be explicitly obtained at //some// point.
  
 ===== Future Scope ===== ===== Future Scope =====
  
-Currently PHP abuses by-reference passing as a way to implement ''inout'' (array_push) and ''out'' (preg_match) parameters. It may be advisable to make these first-class language features instead, thus avoiding some of the pitfalls, as well as performance penalties of references.+==== inout / out parameters ==== 
 + 
 +Currently PHP uses by-reference passing as a way to implement ''inout'' (array_push) and ''out'' (preg_match) parameters. It may be advisable to make these first-class language features in the future, thus avoiding some of the pitfalls, as well as performance penalties of references.
  
 For example the auto-initialization behavior of references is only desired in the case of ''out'' parameters. For ''inout'' parameters it is liable to hide programming mistakes instead. However, the current system is not capable of distinguishing these cases. For example the auto-initialization behavior of references is only desired in the case of ''out'' parameters. For ''inout'' parameters it is liable to hide programming mistakes instead. However, the current system is not capable of distinguishing these cases.
Line 179: Line 273:
 ===== Vote ===== ===== Vote =====
  
-This proposal is a language-change and requires a 2/3 supermajority to pass.+Add support for optional call-site by-reference passing annotations?
rfc/explicit_send_by_ref.1564052238.txt.gz · Last modified: 2019/07/25 10:57 by nikic