Pitch: Multi-statement if/switch/do expressions

The same problem happens with multiple nested then then then. See the example:

class Foo {
  func someMethod() {
    // some code

    func localFunc() -> String {
      func nestedFunc(arg: Int) -> String {
        // code
        return number // why not use `then` here? Can someone think that this return is applied to someMethod()? 
      // code
      let value = ...
      return value // and here?

    // some another code

    let string = nestedFunc(arg: 5)

    let result = do {
      // some code
      then value

This code will better be written in plain old style in my opinion:

let x: Int
if .random() {
  if .random() {
    x = 1
  } else {
    x = 2
} else {
  x = 3

instead of

let x = if .random() {
  then if .random() {
    then 1 // this then applies to the inner if exression
  } else {
    2  // then not needed here, though it would be allowed
} else {

There are no benefits of using if as expression in this case from point of view.
Instead of inventing new keyword and multiple rules of using it we can use simple one rule – don't use if as expression if it can't be used in such way.
The problem then try to solve happens in situations caused by proposition of allowing to omit return. It is simply another spelling of return.

Though, using of do { } blocks as expressions is a nice change.

My point is that

  • firstly new syntax for if / switch statements was invented
  • then we realized it has some problems
  • now new syntax is invented again to resolve problems caused by first. So new convenient short syntax that allows to omit return keyword becomes not so short and convenient. This proposal devalvate benefits introduced by previous.

I think in general implicit return in if / switch should be used for pure expressions.

At least we should search for better solutions in comparison to introduction of then keyword.


I don't think return here require extra cognitive load. The programming itself requires extra cognitive load. And more and more rules introduced by new keywords also require additional cognitive load.

1 Like

The break 42 idiom may be familiar to Ruby devs but would cause a Java dev to go looking for the label 42 - they have a labelled break, instead of C's goto.

I hate the then statement because it triggers a confusing association with if.

Using the semicolon to have a bare value return works for me - this is not an idiom we should encourage so it deserves to be a little big ugly stop-and-thinkish.

Alternatively, rather than the then keyword why not reuse the arrow from declaring function return types?

It would be enough to catch someone's eye and make them think but doesn't introduce as much dissonance as then.

let width = switch scalar.value {
    case 0..<0x80: 1
    case 0x80..<0x0800: 2
    case 0x0800..<0x1_0000: 3
      log("this is unexpected, investigate this")
      -> 4  // arrow for return type already has association with idea of returning a value
1 Like

No need to invoke Java: Swift has the same labeled break.

See above:

1 Like

For this feature I prefer to think and talk in terms of definite assignment, because I think that tracks the multi-branch rule at its heart.

For assignment I think =, : and := as operators.

So perhaps another option for disambiguation might be the colon, by extension from the case statement implicit assignment:

let x = if big() { 10 } else if small() {100} else {
  let median = median()
  let weight = weight(count)
  : minimum + median * weight

If so, : or := should also be permitted in a single-statement block for clarity and visual redundancy. I do think it's helpful to point backwards somehow to indicate the hoisting of the value outside its scope, because people don't always read complex expressions left to right in order.

let x = if big() { : 10 } else if small() { : 100 } else ... 

(otw, I personally preferred the implicit return of the last statement until it was suggested to be too unwieldy for Swift. I also considered just using the outer variable to assign (and naming return values, as Go does), but perhaps that's a bridge too far.)

If "then" is synonymous with "later", and in case "courtesy"
then will be allowed, at least theoretically the following
could happen (then is here replaced with later):

x = if condition { later 5 } else { later 0 }

It seems a bit odd to me, and we would still have a tautology if
"then" means "as a consequence".

No such problem with use, though:

x = if condition { use 5 } else { use 0 }

Sorry to be negative about then, but it's better to
ventilate everything thoroughly before cementing.

If this assertion is true, then this should be part of the proposal. Perhaps I missed an explanation of why this is the case, because the objections here are largely that it might be confusing, not that it requires major rework.

It is brought up under Alternatives Considered in this pitch and in other proposals that made changes to syntax. Possibly it could be worded a little clearer, but it is there.

I'm not sure what you are talking about. This section says:

The first paragraph says that it may be confusing. The second paragraph says that it might invite further discussion about whether it should also be applied to closure or function returns, which is firstly hypothetical and secondly not “rework” but just additional changes that might be requested. This proposal itself already involves some rework, as I understand the term, including backwards incompatibility and rules that might change depending on language version, and may require source changes to existing Swift code as a result.

This would be ambiguous with types. The following is similar and already in Alternatives Considered.

let x = if big() { 10 } else if small() {100} else {
  let median = median()
  let weight = weight(count)
  ; minimum + median * weight

Possibly “:=“, “=:“, or “=>” could be used instead of “:” to continue an expression if that is taken away as an operator. “->” suggested by others would also be ambiguous. This feels like a departure for Swift, but if you come at it from the perspective that expressions are their own separate sub-language within Swift I could see it. I don’t hate it.

Block Assignment

I think there is an interesting ramification you didn’t bring up. “:=“ feels like assigning a value to a block (an implicit let value).

// := is interesting in that it could be more general
let x = if big() { 10 } else if small() {100} else {
  let median = median()
  let weight = weight(count)
  := minimum + median * weight // assign to block
1 Like

Keep reading the next two paragraphs including:

Apologies if I'm slow here, but I have no idea what this means for these examples.

Could you explain how these examples compile today but mean something different if this proposal was for implicit results? I have no idea what those code samples are supposed to mean currently, because they obviously don't compile as written and I don't think you can make them compile with surrounding context.

This quote is about a third possibility that I don't think anyone in the thread is in favour of, where there are semicolons for all lines except the last one…

Like I said, it could be explained better. It mentions Swift following in Rust’s footsteps with implicit return, but in order to do that you need semicolons to disambiguate.

No, it doesn't say that as written. It says that that it is a possible variant of the last expression rule that is used in Rust. i.e.

("could also be applied to" not "would be required in"). The proposal as written doesn't at all suggest that this rule would be needed in Swift, merely that it is one (unpopular) option. Do you have an explanation as to why this would be required? A serious backwards compatibility issue with existing code, for example?

Trust me that this has been discussed in SE-0380 and other places and phrased the way it is for the reasons I stated. Try to build sample programs that are not ambiguous and you will see what I mean. Enum literals and starting an expression with parentheses are particularly problematic, but there are many other areas. It also causes problems from a language evolution standpoint. The best that could be done is allow particularly unambiguous expressions to be used in implicit return without a semicolon, but that would be a mess and I’m pretty sure it wouldn’t make it through the proposal process.

If I failed to convince you, I’m not going to comment further. I admit it would be nice to see this stated more clearly in the pitch or clarified by someone on the Swift team since implicit return would clearly be popular if possible, but I think their mostly absent here means they want to stay out of the bike-shedding.

Like I said in my very first reply, if there are serious issues with the alternative then they should be in the proposal. That's the point of a proposal, and I don't think people should have to read an arbitrary amount of discussion of previous proposals in order to evaluate this one. I read SE-0380 itself and it didn't cover this at all, merely containing the same vague statements about "stylistic preferences" and "should probably be considered for all such cases (like function and closure return values too)". I don't recall seeing anything particularly concerning in the discussions either. If someone has a compelling summary of issues beyond this then they should post it. And if it's so easy to provide examples of issues then I'm not sure why you haven't just done that so far.


I agree. I take this pitch as mostly getting a feel for community reception and it would be nice if there were a deeper dive in to implicit return which many people feel Swift has already taken steps to embrace.

FWIW, “implicit final value” for if/switch/do is perfectly implementable and initial experimentation suggests source breaks would be minimal but it is source breaking for closures – because currently non-expression multi-line ifs could become the return value of closures, thus changing type inference for the closure. This happened to a very small extent with the original single-expression version. More experimentation would be needed to determine if the same is true of multi-expression, especially with the addition of do.

(@hamishknight might have more thoughts here on the extent of the break)

Exactly. The pitch is currently pushing then because that's my (loosely held) preference, but if the community makes a persuasive case to the language steering group that implicit last value is preferable, that's great.

However, I think we should be wary of putting this incremental change aside for the mire of a larger change, such as adopting implicit last expression return for all function-like things. This was in fact the original motivation for keeping to single-line if expressions in SE-0380. Personally I feel that it's important to get these significant quality-of-life improvements into the hands of developers, when it can be done while preserving future forward-looking possibilities. This is essentially the motivation behind the "good resting place" policy for many evolution proposals.


Extensions to non-nominal types could be supported in the future. This would allow someone to extend Void, making

foo() // func foo() {}

invocation of the property bar on Void rather then inference by the return type.
So there still should be a backup plan for leading dot expressions.

BTW, if we adopt the "last expression" rule those who desperately wants using using "then" or "use", etc could do their own version:

var then: Void { () }

let x = if condition {
} else {
    then 24  // ✅