Table of Contents

PHP RFC: foreach iteration of keys without values

Introduction

When iterating with foreach, it is not possible to retrieve only the keys (and not also the values) without unnecessary extra work or semantic overhead. This RFC proposes the following new syntax that makes this possible.

foreach ($iterable as $key => void) {
}

Although the primary motivation of this RFC is to introduce an explicit way to semantically iterate _only_ on keys, there are also performance benefits for doing this as well. The performance benefits are secondary to the semantic proposal.

Background

At present, there are two main patterns used to iterate over all of the keys of an array, both of which have drawbacks:

foreach ($source as $key => $value) {
    // use the $key, ignore the $value
}

Retrieving the ``$value`` and ignoring it has a performance cost in that an opcode is emitted in compilation (and executed during iteration) to retrieve the value at each iteration. If the ``$value`` is never actually used, this is an unnecessary cost. Note that retrieving the value can be “expensive”, such as if ``$source`` is an iterator.

Additionally, there is a semantic cost, in that even though the ``$value`` is intended to be unused, it still exists. There is no clear indication strictly via code that the value is intended to be unused. Even when the variable is named accordingly, such as ``$unused``, ``$dummy``, ``$ignore``, ``$_``, etc, it is not always clear as to the intent. For example, ``$unused``, ``$dummy``, and `$ignore`` could be all be an accurate representation of the meaning of an array's values, causing unnecessary semantic confusion.

foreach (array_keys($array) as $key) {
}

Retrieving the list of array keys for iteration in this manner is more semantically pure because it clearly indicates intent, but it involves a performance cost as a result of the call to array_keys: an additional traversal of the array, as well as the time and memory cost of allocating/deallocating a new array to receive copies of the keys from the source array.

Also, this method will not work when ``$array`` is not actually an array (such as if it is an iterator; in that case, one would need to call ``array_keys(iterator_to_array($array))``, and now there's even more of the same kind of overhead.

Proposal

In order to iterate over an array (or generator, iterator, or other object that can be iterated) and retrieve only its keys, we add the following syntax:

foreach ($source as $key => void) {
}

By adding this syntax, we gain the ability to iterate over the keys in a performant and semantically appropriate manner.

In the implementation provided (see the implementation link in the header), this is accomplished with a slightly more complicated parser and compilation step.

By not having a target variable that receives the value, an opcode is no longer emitted (or executed) to copy that value from the source. This provides a minor performance enhancement at runtime at the expense of a slightly more complicated parser and compiler for foreach. Because this is a change at the compilation level,

A new ``E_COMPILE_ERROR`` is also added. The error “foreach value target must be variable or void” is emitted if a key is requested and the value target specified is not a variable (or reference to a variable), or void.

Syntax Decisions

The following potential syntaxes have been previously suggested. They are not are being considered for this RFC, for the reasons stated below.

Raw Underscore

foreach ($array as $key => _) {
}

While ``_`` is used in some other languages to signify intent that a result will be ignored, PHP itself does not have that concept. Additionally, since ``_`` is a valid constant name, the above code would be semantically incongruous, because it would imply writing a value to a constant. Thus, it is not an appropriate syntax to consider for this feature.

$ Underscore

foreach ($array as $key => $_) {
}

It has also been suggested to use the variable ``$_`` to indicate that the value should be ignored, and also this could serve as a signal to static analyzers that the value should be ignored. PHP does not have a culture of using ``$_`` to indicate this. Even if that changed, and even if static analyzers understood this, there is no performance benefit to writing the code this way: PHP will still retrieve the value, even though it won't be used.

Empty Target

foreach ($array as $key => ) {
}

Leaving the value target empty visually looks like a mistake, rather than signaling a clear intent.

Null

foreach ($array as $key => null) {
}

In PHP, ``null`` is a value. Semantically, this would be similar to trying to assign to a constant, or a bool, int, float, or string literal. ``void`` is used to mean the complete absence of a value, as in the ``void`` return type.

Examples

•••• TODO ••••

Backward Incompatible Changes

None.

Proposed PHP Version(s)

Next PHP 8.x.

RFC Impact

To the Ecosystem

There are no backward-incompatible changes, so end-user code will not need changes, and will observe no impact.

The only change to opcodes emitted is that when the foreach value target is void, the opcode that sets the target variable is omitted. Accordingly, there should be no adverse impact to opcache. (••• this statement was correct in 2016, but it might be bogus now. verify. •••)

Code that reads and interprets PHP source code (such as IDEs, language servers (LSPs), static analyzers, auto-formatters, linters, and related userland PHP libraries) will need to: * add support for the `foreach($iterable as $key => void)` syntax; * infer that the `foreach($iterable as $key => void)` loop will be iterated with only the `$key`, and no value being set.

To Existing Extensions

There should be no impact to extensions as a result of this syntax addition.

To SAPIs

There should be no impact to SAPIs as a result of this syntax addition.

Open Issues

There are no open issues at this time.

Future Scope

Iterating over an `Iterator` or a generator will still cause the creation of each value, even though they are not emitted into the `foreach` loop. This might be expensive. It might be possible to extend the relevant APIs to allow explicitly iterating over keys, so that expensive values do not need to be produced when they are not used.

`foreach($iterable as void)`, where the loop body is called once per each element in the iterable, without receiving any keys or values, is technically feasible, but is not proposed by this RFC.

Voting Choices

Pick a title that reflects the concrete choice people will vote on.

Please consult the php/policies repository for the current voting guidelines.


Primary Vote requiring a 2/3 majority to accept the RFC:

Implement foreach-void as outlined in the RFC?
Real name Yes No Abstain
Final result: 0 0 0
This poll has been closed.

Patches and Tests

The GitHub Pull Request for this change is here: https://github.com/php/php-src/pull/XXX

While in draft, the direct link to the relevant tree is: https://github.com/jbafford/php-src/tree/foreachvoid

Implementation

After the RFC is implemented, this section should contain:

  1. the version(s) it was merged into
  2. a link to the git commit(s)
  3. a link to the PHP manual entry for the feature

References

* [2020 thread on Internals](https://externals.io/message/111774)

Rejected Features

Keep this updated with features that were discussed on the mail lists.

Changelog

* v0.1: Created in 2016 * v0.2: Revised in 2026. No changes to the core of the proposal, just clarifications of the rationale and inclusion of benchmarks.

••• todo: benchmarks •••