Re: Return expression
| From: | Seifeddine Gmati | Date: | Mon, 02 Feb 2026 08:56:52 +0000 |
| Subject: | Re: Return expression | ||
| References: | 1 2 | Groups: | php.internals |
| Request: | Send a blank email to internals+get-129976@lists.php.net to get a copy of this message | ||
On Mon, 2 Feb 2026 at 01:20, Rob Landers <rob@bottled.codes> wrote:
>
>
>
> On Mon, Feb 2, 2026, at 00:41, Morgan wrote:
>
> Just piping up in a sort of pre-discussion way to see if there might be
> any interest in making "return" an expression - in the same way and for
> many (though not all) of the same reasons that "throw" was made an
> expression in 8.0.
>
> Both return and throw "end the current execution"; the difference is
> that "throw" does it because things went bad, while "return" does it
> because things went well and nothing more needs doing.
>
> So, for example:
>
>
> $result = query('foo') ?? return false;
>
> rather than
>
> if(($result = query('foo')) === null) return false;
>
>
> or
>
> $split = match($len) {
> 0 => throw new UnderflowException("Unexpectedly empty"),
> 1 => return false,
> 2,3,5,7 => return true,
> 4,6,8,9 => $this->smol($len),
> default => $this->foo($len, $scale)
> };
>
>
> instead of
>
> if($len === 0)
> throw new UnderflowException("Unexpectedly empty");
> if($len === 1)
> return false;
> if($len === 2 || $len === 3 || $len === 5 || $len === 7)
> return true;
> if($len === 4 || $len === 6 || $len === 8 || $len === 9)
> $split = $this->smol($len);
> else
> $split = $this->foo($len, $scale);
>
> .
>
> A return expression works entirely by its side-effects (passing control
> and the value of its operand back to the calling scope); its own type is
> "never".
>
> The weirdest behaviour I can see at this early stage is the main way it
> deviates from "reasons to have throw as an expression":
>
> fn($x) => return $x * 2
>
> would work, but not in the way you'd think it does; it expands out to
>
> function($x) {
> return return $x * 2;
> }
>
> Which (like "throw throw") is syntactically legal, but the redundant
> operator is redundant. Semantically, however, it could be problematic:
> The anonymous function's return type is technically "never", since that
> is what the type of its return statement's operand is. But that return
> statement never(!) completes, because said operand causes the function
> to end prematurely, causing the numeric value to be returned instead.
> And, of course, a numeric value is not a "never".
>
> What this will do to type declarations I don't know. But checking that
> the body of an arrow function is a return expression and at least
> notifying the author that it's redundant could be done.
>
> This I guess would also impact ordinary return statements: they are now
> expressions that evaluate to "never", and that could have consequences
> for how return types and return type declarations are determined in
> general...
>
> If necessary, I suppose, the type of a return expression could be that
> of its operand - it's just that no-one will ever see it because
> execution is always abandoned before then.
>
>
> Hi Morgan,
>
> From an observability point of view, you lost me when you said return's type is
> "never".
>
> https://3v4l.org/plpIf#v8.5.0
>
> We can clearly see the type is "null", unless you mean something more abstract?
>
> Are you thinking of return as a function call, so you could write it out like:
>
> return(return($x*2))
>
> I guess in that case, you could conceptually think of its arguments as function that returns
> what to return? Thus the above is just sugar for:
>
> return function() {
> return fn() => $x*2;
> }
>
> Which would just collapse down to:
>
> return $x*2;
>
> I think you could handwave it down to that. The removal of the infinite functions is just an
> optimisation.
>
> — Rob
Hi Rob,
I think
never as a result of return makes complete sense.
never
indicates something does not result in a value, e.g throw or exit
where the control flow is given away, return as an expression would
do the same thing, it will move a value out of this function scope to
the caller, resulting in never within the function. ( the same would
apply if break and continue are to be made expressions in the
future ).
As for return as a function call, i don't think that is needed, once
return <expr> is supported as an expression, you would be able to
return return("foo"); as that is just return with the RHS being
a
parenthesized expression; the reason i think return does not make a
sense as a "function" is because i think function call rules should
not apply to it, e.g what happens if you provide multiple arguments?
Does it throw an ArgumentCountError? This is avoidable by making
return ("a", "b") a parse error.
Cheers.
Seifeddine.