[RFC] Pipe to return

php.internals

Vadim Dvorovenko

213 days ago
Introducing Pipe to return RFC https://wiki.php.net/rfc/pipe_to_return In short, RFC proposes piping expression result to `return` to be used together with pipe operator to reduce cognitive load "Hello World" |> strlen <http://www.php.net/strlen>(...) |> return;
-- Vadim Dvorovenko

Rob Landers

213 days ago
On Sat, Jan 31, 2026, at 06:52, Vadim Dvorovenko wrote:
> Introducing Pipe to return RFC > > https://wiki.php.net/rfc/pipe_to_return > > In short, RFC proposes piping expression result to `return` to be used together with pipe operator to reduce cognitive load > > "Hello World" |> strlen <http://www.php.net/strlen>(...) |> return; > -- > > Vadim Dvorovenko >
Interesting, and thank you for the RFC. As a programmer though, I find being able to scan the left-most columns for return statements quite useful. Having to now also scan the right-most when pipes are involved (which could even mean off the edge of the screen) ... not ideal. I get I could just ... not do that ... but since this seems to be about cognitive load, I think this would actually add additional cognitive load instead of removing it. Often, you need to find where the function returns; more often than you need to read actual pipes. — Rob

Vadim Dvorovenko

205 days ago
31.01.2026 20:45, Rob Landers:
> I get I could just ... not do that ... but since this seems to be > about cognitive load, I think this would actually add additional > cognitive load instead of removing it. Often, you need to find where > the function returns; more often than you need to read actual pipes.
03.02.2026 22:13, Tim Düsterhus:
> That said, I agree with the others that I find the proposal a step > backwards in terms of readability. Having the `return` at the start of > the line is a clear indicator that the function ends there and it is > only a “single bit” of information I have to keep in mind while I > continue reading until I find the semicolon ending the statement. It > is also consistent with how assignments - the other primary use case > for pipes - start with the target variable and the equals before the > “pipe expression”. I think it would not be wrong to think of `return` > as “assigning to the return value”.
Thanks for these comments. I find them among the most valuable in this discussion. They really made me think for a long time and experiment a lot. To discuss them, I've been going through and trying to come up with different code options. Unfortunately, there aren't any successful examples yet in the real projects and libraries I work with. If you could provide links to repositories where pipe operator is actively used, I'd be very grateful. So for now I apologize for the fictitious example. Let's take this complex function     function process_request($request)     {         if ($request instanceof ManageEmployeeRequest) {             if ($request instanceof CreateEmployeeRequest) {                 return $request |> logRequest(...) |> validateCreateEmployeeRequest(...) |> processCreateEmployee(...) |> formatEmployeeDataAsArray(...) |> convertArrayToJsonResponse(...);             } elseif ($request instanceof UpdateEmployeeRequest) {                 return $request |> logRequest(...) |> validateUpdateEmployeeRequest(...) |> processUpdateEmployee(...) |> formatEmployeeDataAsArray(...) |> convertArrayToJsonResponse(...);             } elseif ($request instanceof DeleteEmployeeRequest) {                 $request |> logRequest(...) |> validateDeleteEmployeeRequest(...) |> processDeleteEmployee(...);                 return [] |> convertArrayToJsonResponse(...);             } else {                 throw new InvalidArgumentException("Unknown client request type");             }         }         if ($request instanceof ManageClientRequest) {             if ($request instanceof CreateClientRequest) {                 return $request |> validateCreateClientRequest(...) |> processCreateClient(...) |> formatClientDataAsArray(...) |> convertArrayToJsonResponse(...);             } elseif ($request instanceof UpdateClientRequest) {                 return $request |> validateUpdateClientRequest(...) |> processUpdateClient(...) |> formatClientDataAsArray(...) |> convertArrayToJsonResponse(...);             } elseif ($request instanceof DeleteClientRequest) {                 $request |> validateDeleteClientRequest(...) |> processDeleteClient(...);                 return convertArrayToJsonResponse([]);             } else {                 throw new InvalidArgumentException("Unknown client request type");             }         }         throw new InvalidArgumentException("Unknown request type");     } When we read this function, we are actually more interested in the structure of conditions and returns than in the actual actions. In this case, of course, return at the beginning of the line is indeed more readable. Here I completely agree with you. But in real life we ​​won't use such long lines and the chain will be split into several lines. Here I used different code styles in one block for clarity.     function process_request($request)     {         if ($request instanceof ManageEmployeeRequest) {             if ($request instanceof CreateEmployeeRequest) {                 $request                     |> logRequest(...)                     |> validateCreateEmployeeRequest(...)                     |> processCreateEmployee(...)                     |> formatEmployeeDataAsArray(...)                     |> convertArrayToJsonResponse(...)                     |> return;             } elseif ($request instanceof UpdateEmployeeRequest) {                 return                     $request                         |> logRequest(...)                         |> validateUpdateEmployeeRequest(...)                         |> processUpdateEmployee(...)                         |> formatEmployeeDataAsArray(...)                         |> convertArrayToJsonResponse(...);             } elseif ($request instanceof DeleteEmployeeRequest) {                 $loggedRequest = logRequest($loggedRequest);                 $validatedRequest = validateDeleteEmployeeRequest($loggedRequest);                 processDeleteEmployee($validatedRequest);                 $response = convertArrayToJsonResponse([]);                 return $response;             } else {                 throw new InvalidArgumentException("Unknown client request type");             }         }         // ...     } In my case, the IDE brightly highlights 'if', 'elseif', and 'return' keywords. In this version, I clearly see the return in the first two blocks, despite its different placement. The 'return' in the third block stands out the least; it blends in with the preceding text because there is no indentation. But when I'm composing a letter, I don't have syntax highlighting, but the feeling is the same. The third block looks more like a chain of actions. Of course, it's written in imperative style. The first block also looks like a chain of actions. Vision very quickly stops paying attention to `|>`. Moreover, it fits seamlessly into the conditions structure. It's clear that this chain of transformations is executed under these very conditions. And here it seems to me very fortunate that after reading the block to the end, we see return, and we don’t need to look up 6 lines above to remember why we performed all these actions. I myself reread the second block several times, trying to understand whether it really is more convenient to read. But every time I read the chain of actions to the end, I caught myself thinking that I didn’t want to go back to check if there was a ‘return’ there. On the contrary, during the time I was working with this example, my brain developed a rule, and seeing a vertical chain of '|>', I immediately began to look at the end of the block to understand whether there is a return at the end. It's like looking at the first and last lines of a third block, skipping over everything in between. But with `|>`, your eye kind of slides down them, and you get to `|> return;` much faster. I also noticed that when I look at the second block, my vision selects the block starting with `$request`. For some reason it crops the `return` located to the left and above. I think this is because it is a keyword that has ceased to carry meaning for us, and we perceive it as a punctuation mark, rather than as carrying meaning.

Jordi Kroon

213 days ago
Hello, First off, I appreciate the work that has gone into this RFC. I like the general direction and I am not fully against the idea. That said, I am hesitant to introduce another way to express the same thing, especially when it affects readability. Speaking only for myself, I agree with Rob’s point that I expect return to appear on the left side. Placing return on the right side inside the pipe makes the statement harder to scan and can be confusing at first glance. More importantly, I do not think the responsibility for returning should live inside the pipe. Returning is control flow, while the pipe’s core purpose is transforming data. With this change, the pipe no longer clearly communicates “transform this value,” but can instead become responsible for ending execution. I would rather see the pipe produce a result, and keep the return outside the pipe so the intent stays explicit. Also, while the example below obviously would not work, it highlights the potential ambiguity this introduces. In a long pipeline, it becomes less clear where returning happens and what role the pipe is playing. function giveMeFourtyTwo() { return 42 |> return; } Also if you put this into the short `fn` syntax. It will break for the same reasons as above. fn () => 'hello world' |> strlen(...) |> return; Regards, Jordi

Vadim Dvorovenko

212 days ago
Jordi Kroon:
> I like the general direction and I am not fully against the idea. That > said, I am hesitant to introduce another way to express the same > thing, especially when it affects readability.
When we are speaking about pipe operator, it all looks as trying to introduce another way to express the same thing, and it affects readability. As for me,   $temp = "Hello World";   $temp = htmlentities($temp);   $temp = str_split($temp);   $temp = array_map(strtoupper(...), $temp);   return $temp; and   return array_map(strtoupper(...), str_split(htmlentities("Hello World"))); were quite effective ways of expressing value transformation flow. But that RFC was adopted. My RFC is an attempt to quickly eliminate the cognitive side effects arisen by adding the pipe operator to the language. The addition of the pipe operator didn't immediately force all language users to use it. But it did give them the option. Which method of expression of same thing sould be used in different cases, most likely will be described later in the coding standards of specific projects. Likewise, the method proposed here will not be mandatory for use, but there may be cases where it will be chosen for greater readability.
> More importantly, I do not think the responsibility for returning > should live inside the pipe. Returning is control flow, while the > pipe’s core purpose is transforming data. With this change, the pipe > no longer clearly communicates “transform this value,” but can instead > become responsible for ending execution. I would rather see the pipe > produce a result, and keep the return outside the pipe so the intent > stays explicit. > > Also, while the example below obviously would not work, it highlights > the potential ambiguity this introduces. In a long pipeline, it > becomes less clear where returning happens and what role the pipe is > playing. > > function giveMeFourtyTwo() { > return 42 > |> return; > } > Also if you put this into the short `fn` syntax. It will break for the > same reasons as above. > > fn () => 'hello world' > |> strlen(...) > |> return;
Our language is now very flexible. But such flexibility would be impossible if everithing was strictly divided into categories. Take the `throw`, for example. It's obviously intended for flow control. However, it can be used anywhere, like in following example. return 42 |> fn($val) => throw new RuntimeException($val); // or throw throw throw new Exception(); Although such a statements contains ambiguity between return and throw, or between multiple throws, we don't criticize the language for this possibility. In such cases, we say the author of the code has chosen an unfortunate style of expression. My proposal, on the contrary, does not allow using two types of return in one expression or using return inside an arrow function. In this sense, my proposal is quite strict. You can use either way of expression, but not both at the same time. Thus, ambiguity is generated not by the programming language itself and its capabilities, but by those who express their thoughts in it. We should not limit the expressive instruments of a language simply out of fear that they will be misused.
-- Vadim Dvorovenko

Vadim Dvorovenko

212 days ago
Jordi Kroon:
> function giveMeFourtyTwo() { > return 42 > |> return; > } > ... > fn () => 'hello world' > |> strlen(...) > |> return;
Thanks for examples. I've already had a test for first case (https://github.com/vadimonus/php-src/blob/pipe-to-return/Zend/tests/pipe_operator/pipe_to_return/two_returns.phpt) Now i have added test for second case (https://github.com/vadimonus/php-src/blob/pipe-to-return/Zend/tests/pipe_operator/pipe_to_return/008.phpt) to be sure it's not valid and cannot be accidently treated as returning of (fn () => 'hello world' |> strlen(...)) from function as callable
-- Vadim Dvorovenko

Tim Düsterhus

210 days ago
Hi Am 2026-01-31 06:52, schrieb Vadim Dvorovenko:
> Introducing Pipe to return RFC > > https://wiki.php.net/rfc/pipe_to_return > > In short, RFC proposes piping expression result to `return` to be used > together with pipe operator to reduce cognitive load
Thank you for your RFC. I appreciate how well-written it is and how carefully you followed the suggested template. That said, I agree with the others that I find the proposal a step backwards in terms of readability. Having the `return` at the start of the line is a clear indicator that the function ends there and it is only a “single bit” of information I have to keep in mind while I continue reading until I find the semicolon ending the statement. It is also consistent with how assignments - the other primary use case for pipes - start with the target variable and the equals before the “pipe expression”. I think it would not be wrong to think of `return` as “assigning to the return value”. While it is true that no one is forced to use the new syntax, I will inevitably come across it in libraries that decide to use it, thus I will be forced to know that it is a thing and to understand what it does. And of course all IDEs and static analyzers will need to learn about it as well. For these reasons, I don't believe it meets the cost-benefit ratio. Best regards Tim Düsterhus

Vadim Dvorovenko

205 days ago
03.02.2026 22:13, Tim Düsterhus:
> That said, I agree with the others that I find the proposal a step > backwards in terms of readability. Having the `return` at the start of > the line is a clear indicator that the function ends there and it is > only a “single bit” of information I have to keep in mind while I > continue reading until I find the semicolon ending the statement. It > is also consistent with how assignments - the other primary use case > for pipes - start with the target variable and the equals before the > “pipe expression”. I think it would not be wrong to think of `return` > as “assigning to the return value”.
Some time ago, I raised dicussion (https://news-web.php.net/php.internals/128141) of using pipes for assignment as well, you also took part in that discussion. Currently, i started with the RFC for `return` because it's simplest, to illustrate main cognitive problem. By the way, returning is something that can not be implemented function like assign_to. The main idea is that the pipe operator's main drawback is that it changes the usual reading direction. Function composition via + doesn't have this drawback. Maybe if function composition had been implemented earlier, the pipe operator wouldn't have been adopted. Now the pipe operator is part of the language, and I'm just trying to expand its use cases to write more intuitive and native-language-like code. Look, even simple statements like `return $x |> strlen(...);` may be confusing, if you were not familiar with the pipe operator before, because you do not understand, if you should stop after `return $x` or not. On the other side `$x |> strlen(...) |> return` is more intuitive in this way, even if you see |> for the first time, reading this line you understand, that it has two actions, defined by arrows, and it is some sort of data transfer. Even though you know `|>` is called a pipe operator, the `return $x |> strlen(...);` expression doesn't show how the data is flowing through the pipes. Something like `return strlen(...) <| $x` would be much more expressive in this sense. I had an idea to bring an RFC for the pipe operator to the left instead of this. But i chose not to invent a new operator, but simply to expand the grammatical possibilities of using an existing one.

Vadim Dvorovenko

205 days ago
03.02.2026 22:13, Tim Düsterhus:
> While it is true that no one is forced to use the new syntax, I will > inevitably come across it in libraries that decide to use it, thus I > will be forced to know that it is a thing and to understand what it > does. And of course all IDEs and static analyzers will need to learn > about it as well.
Of course, I can't cover all areas, especially proprietary IDEs. However, I planned to prepare corresponding changes for the following tools: nikic/PHP-Parser, psalm, phpstan, php-cs-fixer. If this could affect the vote, I can prepare changes to the this projects before the vote.

Weedpacket

209 days ago
On 2026-01-31 18:52, Vadim Dvorovenko wrote:
> Introducing Pipe to return RFC > > https://wiki.php.net/rfc/pipe_to_return > > In short, RFC proposes piping expression result to `return` to be used > together with pipe operator to reduce cognitive load > > "Hello World" |> strlen <http://www.php.net/strlen>(...) |> return; > > -- > > Vadim Dvorovenko >
I'm wondering how this would relate to the function composition operator, noted in the follow-up RFC. Basically, would "return" make sense at the end of a function composition chain, and what sense would that be? When $x |> f1(...) |> f2(...) |> f3(...); is equivalent to (f1(...) + f2(...) + f3(...))($x); [Aside: personally I find the suggested order of arguments backwards from every other instance of function composition I've encountered, but I suppose I could learn to live with it. But that's another subject.] and $c = f1(...) + f2(...) + f3(...); is equivalent to $c = fn($x) => ($x |> f1(...) |> f2(...) |> f3(...)); what happens when "f3(...)" is "return"? The intended behaviour is given for the first case but not for the others (not even the fourth, which is still the pipe operator).

Vadim Dvorovenko

205 days ago
04.02.2026 04:58, Morgan:
> I'm wondering how this would relate to the function composition > operator, noted in the follow-up RFC. > Basically, would "return" make sense at the end of a function > composition chain, and what sense would that be? > > When $x |> f1(...) |> f2(...) |> f3(...); > is equivalent to (f1(...) + f2(...) + f3(...))($x); > [Aside: personally I find the suggested order of arguments backwards > from every other instance of function composition I've encountered, > but I suppose I could learn to live with it. But that's another subject.] > > and $c = f1(...) + f2(...) + f3(...); > is equivalent to $c = fn($x) => ($x |> f1(...) |> f2(...) |> f3(...)); > > what happens when "f3(...)" is "return"? The intended behaviour is > given for the first case but not for the others (not even the fourth, > which is still the pipe operator).
Neither return nor pipe to return can appear inside an arrow function. So  `fn($x) => ($x |> f1(...) |> f2(...) |> return)` is invalid, same as `fn($x) => (return $x |> f1(...) |> f2(...))` `$x |> f1(...) |> f2(...) |> return;` can be rewritten as `return (f1(...) + f2(...))($x)` or as `(f1(...) + f2(...))($x) |> return`. Piping to return should not be perceived as a method of composition. It is some way of defining data flow, pipe here in plumbing meaning.  $x var goes to f1(), then to f2(), and then to sewerage (return).

Oleksii Bulba

209 days ago
On Sat, Jan 31, 2026 at 9:52 AM Vadim Dvorovenko <vadim.dvorovenko@gmail.com> wrote:
> Introducing Pipe to return RFC > > https://wiki.php.net/rfc/pipe_to_return > > In short, RFC proposes piping expression result to `return` to be used > together with pipe operator to reduce cognitive load > > "Hello World" |> strlen <http://www.php.net/strlen>(...) |> return; > > -- > > Vadim Dvorovenko >
Hi Vadim, thanks for the RFC — the idea of piping into return is interesting. One question about semantics: what would be the expected behavior if there is another pipe operand after return? For example: $foo |> return |> bar(...); Would this be a compile-time error, a no-op after return, or is return intended to be a hard terminal in the pipe chain by definition? Best regards, Oleksii

Vadim Dvorovenko

205 days ago
04.02.2026 15:16, Oleksii Bulba пишет:
> > One question about semantics: what would be the expected behavior if > there is another pipe operand after |return|? For example: > > |$foo |> return |> bar(...);| > > Would this be a compile-time error, a no-op after |return|, or is > |return|intended to be a hard terminal in the pipe chain by definition? > > Best regards, > Oleksii >
This would lead to parse-time time error, `return |> ` is not valid grammar sequence.

Vadim Dvorovenko

205 days ago
Since this raised a lot of questions, I added examples of invalid uses of the proposed construction from your messages to the RFC. https://wiki.php.net/rfc/pipe_to_return#invalid_use_cases