Re: Return expression

From: Date: Mon, 02 Feb 2026 00:18:32 +0000
Subject: Re: Return expression
References: 1  Groups: php.internals 
Request: Send a blank email to internals+get-129973@lists.php.net to get a copy of this message
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

« previous php.internals (#129973) next »