Like I said, it’s subjective. I agree that let
/ in
is the weakest in motivation. guard
expressions would be useful though and would make sense with in
following the else
expression I think. And if I can guard let
/ in
then there is a consistency argument for also being able to let
/ in
as well.
I don’t know where the “in” is coming from, but you can use the ternary operator to model guard:
func doIt(_ x: Int) -> String {
guard x < 0 else { “pos” }
“neg”
}
// is the same as:
func doIt(_ x: Int) -> String {
x < 0 ? “neg” : “pos”
}
For more complex expressions, I don’t see the benefit over the “return” keyword. It’s short, explicit and familiar to almost every programmer in the world.
I do not support change for the sake of change.
Of course you can use ternary but that’s besides the point. The whole topic of conditional expressions was revived precisely because a lot of people don’t like ternary and find it hard to read. You could also use an if
expression here, but in some cases guard
communicates intent a lot more clearly. That said - I did say that we should focus on if
and switch
expressions before considering guard
and let
expressions.
The in
serves as a connective between the else block and the main body of a guard
expression and is inspired by ML-ish syntax. I think placing the main subexpression immediately following the closing }
would be awkward. FWIW, in
would only be required by guard
expressions, not guard
statements.
guard
statements also do not have to return a value. They can throw
or use one of the various abort functions. A ternary expression must return a value.
Thanks a lot for this, it's an excellent case study, and here's my two cents.
From a purely stylistic perspective, single line computed properties look ugly if the type is repeated both in type declaration and constructor, for example in
public var debugDescription: String { String(self).debugDescription }
doesn't look particularly good due to the String { String(
portion: from a code style perspective, I'd favor something like
public var debugDescription: String { .init(self).debugDescription }
The __ Control Flow__ section shows the painful state we're in for the lack of if/else
and switch
expression, for example this
static func ==(
_ lhs: _StringComparisonResult, _ rhs: _StringComparisonResult
) -> Bool {
switch (lhs, rhs) {
case (.equal, .equal): return true
case (.less, .less): return true
default: return false
}
}
public func distance(from i: Index, to j: Index) -> Int {
if _fastPath(_guts.isFastUTF8) {
return j._encodedOffset &- i._encodedOffset
}
return _foreignDistance(from: i, to: j)
}
would be clearer if written like this
static func ==(
_ lhs: _StringComparisonResult, _ rhs: _StringComparisonResult
) -> Bool {
switch (lhs, rhs) {
case (.equal, .equal): true
case (.less, .less): true
default: false
}
}
public func distance(from i: Index, to j: Index) -> Int {
if _fastPath(_guts.isFastUTF8) {
j._encodedOffset &- i._encodedOffset
} else {
_foreignDistance(from: i, to: j)
}
}
I’m going to stand up and disagree.
As a personal, subjective opinion, I find that omitting return
from a multi-line function is undesirable and decreases clarity.
Even for a computed property with just a single expression, if it spans more than one vertical line of code, I would strongly prefer to include return
.
Essentially, the *only* time I see any benefit from omitting return
, is when the entire body of the scope is on a single line of code. In that case, the reader knows at a glance that there is only one thing happening, and it must be returning a value.
The moment the body spans more than one line, if return
be omitted, suddenly the reader must consciously, cognitively parse the entire multi-line body to determine whether it is or is not a single expression, and that constitutes a reduction in readability, a loss of clarity, and an increase in mental burden.
I agree with this.
On a more general note, I don’t think that trying to turn Swift into an expression-oriented language incrementally is a good idea. SE-0255 was a misguided step in this direction; if
and switch
expressions, and now ideas like let
…in
, amount to an attempt to override a core design decision of the language step by step.
Having an agenda isn’t automatically a bad thing, but it should be clearly expressed (hah) though a manifesto or similar.
The point is, if if/else
and switch
were expressions, than a function body starting with those keyword would be a single expression.
This case is similar to using a plain method instead: it would span multiple lines for readability, but it would still be a single expression, for example
static func ==(
_ lhs: _StringComparisonResult, _ rhs: _StringComparisonResult
) -> Bool {
Pair(lhs, rhs).fold(
onBothEqual: true,
onBothLess: true,
otherwise: false
)
}
public func distance(from i: Index, to j: Index) -> Int {
_fastPath(_guts.isFastUTF8).if(
true: j._encodedOffset &- i._encodedOffset,
false: _foreignDistance(from: i, to: j)
)
}
Yes, those are exactly the sort of multi-line bodies for which I am strongly opposed to omitting return
.
Why not? But there's no need for a full plan here, we could literally just turn if/else
and switch
into expressions and make a huge chunk of the Swift community happy, myself included: I write code that would greatly benefit from this on a daily basis.
Also, this is like normal, regular stuff in languages that partially compete with Swift, for example Kotlin.
I agree with this. Just because there has been discussion of some possible future directions does not mean there is “an agenda” or that these directions must be pursued. In fact I encouraged that we don’t pursue these directions right now because I think they will be too controversial. if
and switch
expressions would deliver enormous benefit. We should pursue these as soon as we can find somebody to work on an implementation.
And nobody is proposing that you be required to omit return
here. This is a choice that should be left up to individual teams, not dictated by the language. I imagine linters would add a rule allowing you to ban return
omission if enough people really don’t like it.
That’s not the point.
Someone upthread claimed that if
/ switch
expressions would make certain functions *clearer* by allowing the omission of return
.
I am saying that in the examples given, omitting return
would make the functions *less clear*. The change proffered as a benefit, appears to me a detriment. I am disputing the very premise of their assertion.
If you want to make a *different* argument for if
/ switch
expressions, by all means do so (preferably in a new, dedicated thread for that purpose).
But the argument seen here, that they would enable people to write lengthy multi-line functions as a single expression, and thus omit return
, thereby requiring readers of that function to studiously examine the entire vertical extent of its body in order to comprehend whether it comprises multiple statements or just a single expression, strikes me as a negative.
I agree that particularly long expressions can be difficult to parse in such situations. However, one often has method chaining, which is pretty easy to read when spread across multiple lines, especially if you use an editor like Xcode, which indents successive lines one more level. The chain is thus clearly subordinate.
At the moment, I am on the fence with regards to allowing (other forms of) multi-line expressions to omit return, but if it came down to a vote, I think I'd go with allowing it. If it's really that difficult to follow, I'd hope the original developer would think so, too. In addition, much of the benefit of omitting return
is lost if the expression spans multiple lines anyway.

I don't feel like that extends to
let
/in
since it isn't simpler syntactically than the thing you started with and doesn't give you any additional guarantees besides "this is an expression".
There is one additional guarantee that could be had from such a feature:
let view =
let body = someBigFunction(param1, param2, param3)
in makeLabel(text: body)
This restricts the scope of the body
variable to a single expression, in what might otherwise might be a long function.
Having said that, two =
in one expression looks terrible (consider this on a single line), and I think we would get much of the same value from a do {}
expression, or even calling an inline closure without any language changes.

let view =
let body = someBigFunction(param1, param2, param3)
in makeLabel(text: body)
This is a great example of why local bindings are beneficial! Limiting scope of identifiers is always a good thing and doing that is clunky today because trailing closure syntax is the only option. I really like this example because it shows how supporting local bindings in expressions is actually most beneficial outside of the context of single-expression bodies where return
may be elides. It most useful when you have a longer body with many statements.
However, I think this example actually would be better written with trailing local binding syntax (using with
here instead of where
since where
in Swift is used with predicates and local bindings are not predicates):
let view = makeLabel(text: body) with
body = someBigFunction(param1, param2, param3)
The reason I think this works better is that it maintains the direct syntactic relationship of view
with the result of makeLabel
.
For contrast, here is the clunky syntax we have to use today if we want to factor out a subexpression while restricting its scope to the immediate context (note: we even had to explicit type annotation):
let view: String = {
let body = someBigFunction(param1, param2, param3)
return makeLabel(text: body)
}()
In my opinion, this seems like we're getting closer to wishing there was a clearer way to use immediately evaluated closures. If you didn't have to wait until the end of the scope to see that it is evaluated… it would do the job of with
as you've described perfectly.