# RFC: Dedicated syntax for variadic functions

- Status: Implemented (PHP 5.6)
- Authors: Nikita Popov nikic@php.net
- Date: 2013-08-27
- Original: https://wiki.php.net/rfc/variadics
- Page: https://externals.io/rfc/variadics
- More for agents: search with https://externals.io/search.md?q={query} · RFCs at https://externals.io/rfc.md · JSON API at https://externals.io/api · documentation at https://externals.io/llms.txt

## Votes

### Should the proposed variadic-function syntax be added in PHP 5.6 (master)? (closed)

Yes: 36 · No: 1

Voters: auroraeosrose (Yes), blanchonvincent (Yes), bukka (Yes), colder (Yes), datibbaw (Yes), derick (Yes), dmitry (Yes), drak (Yes), fabpot (Yes), hradtke (No), indeyets (Yes), jpauli (Yes), jwage (Yes), kalle (Yes), kassner (Yes), klaussilveira (Yes), krakjoe (Yes), leszek (Yes), levim (Yes), mike (Yes), nikic (Yes), pajoye (Yes), peehaa (Yes), pollita (Yes), rasmus (Yes), rdohms (Yes), reeze (Yes), remi (Yes), sean (Yes), seld (Yes), stas (Yes), toby (Yes), treffynnon (Yes), weierophinney (Yes), wez (Yes), yohgaki (Yes), zeev (Yes)

## Discussions on the mailing list

- [\[RFC\] Syntax for variadic functions](https://externals.io/message/68639.md) — started on 2013-08-28, 24 messages
- [\[VOTE\] Syntax for variadic functions](https://externals.io/message/69154.md) — started on 2013-09-16, 2 messages
- [PHP RFC: Deffering functions by arguments (count or/and type)](https://externals.io/message/70014.md) — started on 2013-11-05, 7 messages
- [Deprecation of func_get_args(), call_user_func_array() and related API](https://externals.io/message/77854.md) — started on 2014-10-10, 29 messages

---

## Proposal

### Introduction

Currently variadic functions are implemented by fetching the function arguments using `func_get_args()`. The following code sample shows an implementation of a variadic function used to prepare and execute a MySQL query (I'll be making use of this example throughout the RFC):

```php
class MySQL implements DB {
    protected $pdo;
    public function query($query) {
        $stmt = $this->pdo->prepare($query);
        $stmt->execute(array_slice(func_get_args(), 1));
        return $stmt;
    }
    // ...
}
 
$userData = $db->query('SELECT * FROM users WHERE id = ?', $userID)->fetch();
```

There are two issues with the above approach:

Firstly, by just looking at the function signature `public function query($query)` you cannot know that this is actually a variadic function. You'd think that the function can only run a normal query and doesn't support bound parameters.

Secondly, because `func_get_args()` returns \*all\* arguments passed to the function you first need to remove the `$query`parameter using `array_slice(func_get_args(), 1)`.

This RFC proposed to solve these issues by adding a special syntax for variadic functions:

```php
class MySQL implements DB {
    public function query($query, ...$params) {
        $stmt = $this->pdo->prepare($query);
        $stmt->execute($params);
        return $stmt;
    }
    // ...
}
 
$userData = $db->query('SELECT * FROM users WHERE id = ?', $userID)->fetch();
```

The `...$params` syntax indicates that this is a variadic function and that all arguments after `$query` should be put into the `$params` array. Using the new syntax both of the issues mentioned above are solved.

### Population of variadic parameter

The following example shows how the variadic parameter `...$params` is populated depending on the number of passed arguments:

```php
function fn($reqParam, $optParam = null, ...$params) {
    var_dump($reqParam, $optParam, $params);
}
 
fn(1);             // 1, null, []
fn(1, 2);          // 1, 2, []
fn(1, 2, 3);       // 1, 2, [3]
fn(1, 2, 3, 4);    // 1, 2, [3, 4]
fn(1, 2, 3, 4, 5); // 1, 2, [3, 4, 5]
```

`$params` will be an empty array if the number of passed arguments is smaller than the number of declared parameters. Any further arguments will be added to the `$params` array (in the order in which they were passed). The `$params` array is using continuous zero-based indices.

### By-reference capture

The new syntax additionally adds support for capturing variadic arguments by-reference, something that was previously not possible in userland code. Only internal functions could make use of this via `ZEND_ACC_PASS_REST_BY_REF`.

This would allow implementing functions like `sscanf()` or `mysqli_stmt::bind_param()` in userland. The following method uses the new syntax to prepare a query and bind parameters by-reference to it:

```php
class MySQL implements DB {
    public function prepare($query, &...$params) {
        $stmt = $this->pdo->prepare($query);
        foreach ($params as $i => &$param) {
            $stmt->bindParam($i + 1, $param);
        }
        return $stmt;
    }
    // ...
}
 
$stmt = $db->prepare('INSERT INTO users (name, email, age) VALUES (?, ?, ?)', $name, $email, $age);
foreach ($usersToInsert as list($name, $email, $age)) {
    $stmt->execute();
}
```

A by-reference capture of variadic arguments is indicated by a `&` before the ellipsis `...`.

### Type hints

Furthermore it's possible to provide a typehint that all variadic arguments are checked against. E.g. this is how the signature of `array_merge` implemented in userland would look like:

```php
function array_merge(array ...$arrays) { /* ... */ }
```

PHP would make sure that all arguments are actually arrays. This also works for all other typehints like `callable ...$callbacks` or `Route ...$routes`.

### Prototype checks

An advantage of declaring variadics in the parameter list is that the signature can be enforced by intefaces and during inheritance. The `MySQL::query()` and `MySQL::prepare()` examples above already referenced an interface `DB`. This is how the interface could look like:

```php
interface DB {
    public function query($query, ...$params);
    public function prepare($query, &...$params);
    // ...
}
```

This interface will force any implementation to make both these functions variadic and will also enforce the by-ref capture for `prepare()`.

The exact protoype checks (what is valid and what is not) are outlined below:

```php
// INVALID: Turning a variadic function into a non-variadic one
public function query($query, ...$params)
public function query($query)
 
// VALID: Turning a non-variadic function into a variadic one
public function query($query)
public function query($query, ...$params)
// Note: This is allowed as ...$params is optional and PHP allows additional optional arguments
 
// INVALID: Changing the passing mode for variadic parameters
public function query($query, &...$params)
public function query($query, ...$params)
 
// INVALID: Changing the typehint of a variadic parameter to an incompatible typehint
public function query($query, array ...$params)
public function query($query, callable ...$params)
 
// INVALID: Removing parameter before the variadic parameter
public function query($query, ...$params)
public function query(...$params)
// Note: Personally I don't think this makes sense, but this is how
//       PHP behaves in general, so I'm staying consistent with it
 
// VALID: Adding additional optional parameter before the variadic parameter (with compatible typehint)
public function query($query, array ...$params)
public function query($query, array $extraParam = null, array ...$params)
 
// INVALID: Adding additional optional parameter with incompatible typehint
public function query($query, array ...$params)
public function query($query, callable $extraParam = null, array ...$params)
```

### Syntactic restrictions

There may be only one variadic parameter and it needs to be the last parameter of the function. A variadic parameter may not have a default value.

As such all of the following are invalid:

```php
function fn(...$args, $arg)
function fn(...$args1, ...$args2)
function fn($arg, ...$args = [])
```

### Reflection

Two new methods are added to Reflection:

```php
bool ReflectionFunction::isVariadic()
bool ReflectionParameter::isVariadic()
```

The functions will return `true` if the function/parameter is variadic, `false` otherwise.

### Summary

To sum up, the feature adds the following new syntax:

- `function fn($arg, ...$args)`: Capture all variadic arguments into the `$args` array
- `function fn($arg, &...$args)`: Do the capture by reference
- `function fn($arg, array ...$args)`: Enforce that all variadic arguments are arrays (or some other typehint)
- `function fn($arg, array &...$args)`: Combine both - variadic arguments are arrays that are captured by-reference

The advantages of the syntax are:

- It's immidiately clear that a function is variadic, without having to read documentation.
- It is no longer necessary to `array_slice()` the variadic arguments from `func_get_args()`
- It is now possible to do variadic by-reference captures
- Types can be checked with a typehint (rather than a manual loop)
- Variadic prototypes can be enforce in interfaces / by inheritance

## Backwards compatibility

### Userland

This change does not break backwards compatibility for userland code.

In particular, this RFC does not propose to deprecate or remove the `func_get_args()` family of functions, at least not any time soon.

### Internal

The `pass_rest_by_ref` argument of `ZEND_BEGIN_ARG_INFO` and `ZEND_BEING_ARG_INFO_EX` is no longer used. Instead functions can declare a variadic argument in the arginfo using `ZEND_ARG_VARIADIC_INFO`.

For example, this is how the arginfo for `sscanf()` changed:

```c
// OLD:
ZEND_BEGIN_ARG_INFO_EX(arginfo_sscanf, 1, 0, 2)
    ZEND_ARG_INFO(0, str)
    ZEND_ARG_INFO(0, format)
    ZEND_ARG_INFO(1, ...)
ZEND_END_ARG_INFO()
 
// NEW:
ZEND_BEGIN_ARG_INFO_EX(arginfo_sscanf, 0, 0, 2)
    ZEND_ARG_INFO(0, str)
    ZEND_ARG_INFO(0, format)
    ZEND_ARG_VARIADIC_INFO(1, vars)
ZEND_END_ARG_INFO()
```

It would theoretically be possible to retain support for `pass_rest_by_ref` (by automatically generating a variadic arg), but as this is an exceedingly rarely used feature I don't think this is necessary. All usages of it in php-src have been replaced.

Apart from this the change should be transparent from an internals point of view. Macros like `ARG_MUST_BE_SENT_BY_REF` continue to work.

## Discussion

### Choice of syntax

Presumably this RFC will quickly deteriorate towards a bike-shedding of the best syntax for variadic parameters, so I'll try to explain my choice for the proposed syntax right away:

The use of `...` is already familiar from the PHP documentation, where variadics are denoted using a trailing `$...` parameter. The reason `...` follows \*before\* the parameter in the proposed syntax is to clearly show that the typehint/ref-modifier before it applies to all arguments.

Some possible alternative syntax and why I don't like it:

- `$args...`. With ref-modifier (`&$args...`) this does not show well that the individual arguments are references, rather than `$args` itself. With typehint (`array $args...`) it also looks like the typehint applies to `$args` itself rather than all variadic arguments.
- `*$args`. This is the syntax that both Ruby and Python use. For PHP this does not work well because `*$` is a weird combination. It gets worse with a by-reference capture: `&*$args`. This looks like a random sequences of special characters. Combined with a typehint the syntax looks a lot like a pointer: `Foo *$args`.
- `params $args`. This is what C# does. This would require making `params` a keyword. Furthermore this doesn't have any nice way to declare typehints. In C# this is done using `params type[] args`, but PHP doesn't have `type[]` hints and introducing them only here doesn't seem right.

The proposed syntax is also used by Java and will be used in [Javascript (ECMAScript Harmony proposal)](http://wiki.ecmascript.org/doku.php?id=harmony:rest_parameters). Go and C++ also employ a similar syntax.

## Patch

Patch available in PR#421: <https://github.com/php/php-src/pull/421>

## Vote

The vote started on 16.09.2013 and ended on 23.09.2013. There were 36 votes in favor and one against, as such the necessary two-third majority is met and this feature is **accepted**.

**Vote: Should the proposed variadic-function syntax be added in PHP 5.6 (master)?** — Yes: 36 · No: 1 (closed)

## Argument unpacking

The [argument unpacking RFC](https://externals.io/rfc/argument_unpacking) introduces the following related syntax:

```php
$db->query($query, ...$params);
```
