# \[Pitch\] if and switch expressions

**URL:** <https://forums.swift.org/t/pitch-if-and-switch-expressions/61149>\
**Category:** Pitches\
**Created:** [October 31, 2022, 9:57pm UTC](https://forums.swift.org/t/pitch-if-and-switch-expressions/61149 "2022-10-31T21:57:39Z")\
**Posts on this page:** 20\
**Page:** 1

<div class="post-metadata">

**Author:** ![ben-cohen](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/ben-cohen/32/296_2.png) [@ben-cohen](https://forums.swift.org/u/ben-cohen)\
**Post date:** [October 31, 2022, 9:57pm UTC](https://forums.swift.org/t/pitch-if-and-switch-expressions/61149/1 "2022-10-31T21:57:40Z")

</div>

Hey all, an early-stage pitch for this feature. No implementation quite yet but hopefully not too far off.

# `if` and `switch` expressions

This proposal introduces the ability to use `if` and `switch` statements as expressions, for the purpose of:

- Returning values from functions, properties, and closures;
- Assigning values to variables; and
- Declaring variables.

## Motivation

Swift has always had a terse but readable syntax for closures, which allows the `return` to be omitted when the body is a single expression. [SE-0255: Implicit Returns from Single-Expression Functions](https://github.com/apple/swift-evolution/blob/master/proposals/0255-omit-return.md) extended this to functions and properties with a single expression as their body.

This omission of the `return` keyword is in keeping with Swift's low-ceremony approach, and is in common with many of Swift's peer "modern" languages. However, Swift differs from its peers in the lack support for `if` and `switch` expressions.

In some cases, this causes ceremony to make a return (ahem), for example:

```swift
public static func width(_ x: Unicode.Scalar) -> Int {
  switch x.value {
    case 0..<0x80: return 1
    case 0x80..<0x0800: return 2
    case 0x0800..<0x1_0000: return 3
    default: return 4
  }
}

```

In other cases, a user might be tempted to lean heavily on the harder-to-read ternary syntax:

```swift
let bullet = isRoot && (count == 0 || !willExpand) ? ""
    : count == 0 ? "- "
    : maxDepth <= 0 ? "▹ " : "▿ "

```

Opinions vary on this kind of code, from enthusiasm to horror, but it's accepted that it's reasonable to find this syntax _too_ terse.

Another option is to use Swift's definite initialization feature:

```swift
let bullet: String
if isRoot && (count == 0 || !willExpand) { bullet = "" }
else if count == 0 { bullet = "- " }
else if maxDepth <= 0 { bullet = "▹ " }
else { bullet = "▿ " }

```

Not only does this add the ceremony of explicit typing and assignment on each branch (perhaps tempting overly terse variable names), it is only practical when the type is easily known. It cannot be used with opaque types, and is very inconvenient and ugly if the type is a complex nested generic.

Programmers less familiar with Swift might not know this technique, so they may be tempted to take the approach of `var bullet = ""`. This is more bug prone where the default value may not be desired in _any_ circumstances, but definitive initialization won't ensure that it's overridden.

Finally, a closure can be used to simulate an `if` expression:

```swift
let bullet = {
    if isRoot && (count == 0 || !willExpand) { return "" }
    else if count == 0 { return "- " }
    else if maxDepth <= 0 { return "▹ " }
    else { return "▿ " }
}()

```

This also requires `return`s, plus some closure ceremony. But here the `return`s are more than ceremony – they require extra cognitive load to understand they are returning from a closure, not the outer function.

This proposal introduces a new syntax that avoids all of these problems:

```swift
let bullet =
    if isRoot && (count == 0 || !willExpand) { "" }
    else if count == 0 { "- " }
    else if maxDepth <= 0 { "▹ " }
    else { "▿ " }

```

Similarly, the `return` ceremony could be dropped from the earlier example:

```swift
public static func width(_ x: Unicode.Scalar) -> Int {
  switch x.value {
    case 0..<0x80: 1
    case 0x80..<0x0800: 2
    case 0x0800..<0x1_0000: 3
    default: 4
  }
}

```

Both these examples come from posts by [Nate Cook](https://forums.swift.org/t/if-else-expressions/22366/48) and [Michael Ilseman](https://forums.swift.org/t/omitting-returns-in-string-case-study-of-se-0255/24283), documenting many more examples where the standard library code would be much improved by this feature.

## Detailed Design

`if` and `switch` statements will be usable as expressions, for the purpose of:

- Returning values from functions, properties, and closures (either with implicit or explicit `return`);
- Assigning values to variables; and
- Declaring variables.

There are of course many other places where an expression can appear, including as a sub-expression, or as an argument to a function. This is not being proposed at this time, and is discussed in the future directions section.

For an `if` or `switch` to be used as an expression, it would need to meet these criteria:

**Each branch of the `if`, or each `case` of the `switch`, must be a single expression.**

Each of these expressions become the value of the overall expression if the branch is chosen.

This does have the downside of requiring fallback to the existing techniques when, for example, a single expression has a log line above it. This is in keeping with the current behavior of `return` omission.

An exception to this rule is if a branch either returns, throws, or traps, in which case no value for the overall expression need be produced.

**Each of those expressions, when type checked independently, must produce the same type.**

This has two benefits: it dramatically simplifies the compiler's work in type checking the expression, and it makes it easier to reason about both individual branches and the overall expression.

It has the effect of requiring more type context in ambiguous cases. The following code would _not_ compile:

```swift
let x = if p { 0 } else { 1.0 }

```

since when type checked individually, `0` is of type `Int` and `1.0`. The fix would be to disambiguate each branch. In this case, either by rewriting `0` as `0.0`, or by providing type context e.g. `0 as Double`.

This can be resolved by providing type context to each of the branches:

```swift
  let y: Float = switch x.value {
    case 0..<0x80: 1
    case 0x80..<0x0800: 2.0
    case 0x0800..<0x1_0000: 3.0
    default: return 4.5
  }

```

This decision is in keeping with other recent proposals such as [SE-0244: Opaque Result Types](https://github.com/apple/swift-evolution/blob/main/proposals/0244-opaque-result-types.md):

```swift
// Error: Function declares an opaque return type 'some Numeric', but the
// return statements in its body do not have matching underlying types
func f() -> some Numeric {
    if Bool.random() {
        return 0
    } else {
        return 1.0
    }
}

```

This rule will require explicit type context for declarations in order to determine the type of `nil` literals:

```swift
// invalid:
let x = if p { nil } else { 2.0 }
// valid with required type context:
let x: Double? = if p { nil } else { 2.0 }

```

Of course, when returning from a function or assigning to an existing variable, this type context is always provided.

It is also in keeping with [SE-0326: Enable multi-statement closure parameter/result type inference](https://github.com/apple/swift-evolution/blob/main/proposals/0326-extending-multi-statement-closure-inference.md):

```swift
func test<T>(_: (Int?) -> T) {}

// invalid
test { x in
  guard let x { return nil }
  return x
}

// valid with required type context:
test { x -> Int? in
  guard let x { return nil }
  return x
}

```

It differs from the behavior of the ternary operator (`let x = p ? 0 : 1.0` compiles, with `x: Double`).

However, the impact of bidirectional inference on the performance of the type checker would likely prohibit this feature from being implemented today, even if it were considered preferable. This is especially true in cases where there are many branches. This decision could be revisited in future: switching to full bidirectional type inference may be source breaking in theory, but probably not in practice (the proposal authors can't think of any examples where it would be).

Bidirectional inference also makes it very difficult to reason about each of the branches individuall, leading to sometimes unexpected results:

```swift
let x = if p {
  [1]
} else {
  [1].lazy.map(expensiveOperation)
}

```

With full bidirectional inference, the `Array` in the `if` branch would force the `.lazy.map` in the `else` branch to be unexpectedly eager.

**In the case of `if` statements, the branches must include an `else`**

This rule is consistent with the current rules for definitive initialization and return statements with `if` e.g.

```swift
func f() -> String {
    let b = Bool.random()
    if b == true {
        return "true"
    } else if b == false { // } else { here would compile
        return "false"
    }
} // Missing return in global function expected to return 'String'

```

This could be revisited in the future across the board (to DI, return values, and `if` expressions) if logic similar to that of exhaustive switches were applied, but this would be a separate proposal.

**The expression is not part of a result builder**

`if` and `switch` statements are already expressions when used in the context of a result builder, via the `buildEither` function. This proposal does not change this feature.

## Alternatives Considered and Future Directions

### Sticking with the Status Quo

The list of [commonly rejected proposals](https://github.com/apple/swift-evolution/blob/main/commonly_proposed.md) includes the subject of this proposal:

> **if/else and switch as expressions** : These are conceptually interesting things to support, but many of the problems solved by making these into expressions are already solved in Swift in other ways. Making them expressions introduces significant tradeoffs, and on balance, we haven't found a design that is clearly better than what we have so far.

The motivation section above outlines why the alternatives that exist today fall short. One of the reasons this proposal is narrow in scope is to bring the majority of value while avoiding resolving some of these more difficult trade-offs.

The lack of this feature puts Swift's [claim](https://www.swift.org/about/) to be a modern programming language under some strain. It is one of the few modern languages (Go being the other notable exception) not to support something along these lines.

### Full Expressions

This proposal chooses a narrow path of only enabling these expressions in the 3 cases laid out at the start. An alternative would be to make them full-blown expressions everywhere.

A feel for the kind of expressions this could produce can be found in [this commit](https://github.com/apple/swift/compare/main...hamishknight:express-yourself#diff-7db38bc4b6f7872e5a631989c2925f5fac21199e221aa9112afbbc9aae66a2de) which adds this functionality to the parser.

This includes various fairly conventional examples not proposed here, but also some pretty strange ones such as `for b in [true] where switch b { case true: true case false: false } {}`.

The strange examples can mostly be considered "weird but harmless" but there are some source breaking edge cases, in particular in result builders:

```swift
var body: some View {
    VStack {
        if showButton {
            Button("Click me!", action: clicked)
        } else {
            Text("No button")
        }
        .someStaticProperty
    }
}

```

In this case, if `if` expressions were allowed to have postfix member expressions (which they aren't today, even in result builders), it would be ambiguous whether this should be parsed as a modifier on the `if` expression, or as a new expression. This could only be an issue for result builders, but the parser does not have the ability to specialize behavior for result builders. Note, this issue can happen today (and is why `One` exists for Regex Builders) but could introduce a new ambiguity for code that works this way today.

This proposal suggests keeping the initial implementation narrow, as assignment and returns are hoped to cover at least 95% of use cases. Further cases could be added in later proposals once the community has had a chance to use this feature in practice – including source breaking versions introduced under a language variant.

### Guard

Often enthusiasm for `guard` leads to requests for `guard` to have parity with `if`. Returning a value from a `guard`'s else is very common, and could potentially be sugared as

```swift
  guard hasNativeStorage else { nil }

```

This is appealing, but is really a different proposal, of allowing omission `return` in `guard` statements.

### Multi-statement branches

The requirement that every branch be just a single expression leads to an unfortunate usability cliff:

```swift
let decoded =
  if isFastUTF8 {
    Log("Taking the fast path")
    withFastUTF8 { _decodeScalar($0, startingAt: i) }
  } else
    Log("Running error-correcting slow-path")
    foreignErrorCorrectedScalar(
      startingAt: String.Index(_encodedOffset: i))
  }

```

This is consistent with other cases, like multi-statement closures. But unlike in that case, where all that is needed is a `return` from the closure, this requires the user refactor the code back to the old mechanisms.

The trouble is, there is no great solution here. The approach taken by some other languages such as rust is to allow a bare expression at the end of the scope to be the expression value for that scope. There are stylistic preferences for and against this. More importantly, this would be a fairly radical new direction for Swift, and if proposed should probably be considered for all such cases (like function and closure return values too).

### Either

As mentioned above, in result builders an `if` can be used to construct an `Either` type, which means the expressions in the branches could be of different types.

This could be done with `if` expressions outside result builders too, and would be a powerful new feature for Swift. However, it is large in scope (including the introduction of a language-integrated `Either` type) and should be considered in a separate proposal, probably after the community has adjusted to the more vanilla version proposed here.

---

<div class="post-metadata">

**Author:** ![allevato](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/allevato/32/385_2.png) [@allevato](https://forums.swift.org/u/allevato)\
**Post date:** [October 31, 2022, 10:10pm UTC](https://forums.swift.org/t/pitch-if-and-switch-expressions/61149/2 "2022-10-31T22:10:42Z")

</div>

I'm glad to see this pitch! Shame we can't realistically deprecate ternary `?:` once we have something far better. 🙂

I didn't see it mentioned explicitly, but in these expression-based `if`/`switch`es, are bindings permitted that would be usable within the relevant scopes? For example,

```swift
let x = if let foo = something() { foo } else { someDefaultValue }

let y =
  switch bar {
  case .someCase(let value): value
  case .otherCase(let a, let b): a + b
  }

```

I can't immediately think of a technical/conceptual reason we _wouldn't_ allow this.

---

<div class="post-metadata">

**Author:** ![Nikita\_Leonov](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/nikita_leonov/32/2833_2.png) [@Nikita\_Leonov](https://forums.swift.org/u/Nikita_Leonov)\
**Post date:** [October 31, 2022, 10:17pm UTC](https://forums.swift.org/t/pitch-if-and-switch-expressions/61149/3 "2022-10-31T22:17:10Z")

</div>

@allevato aren't `let x = if ...` just a syntax sugar of `let x = {if ..}()`?  
This means the scope of expression assignment to an `x` is limited to the expression itself and it can't leak `foo` into `let y = switch foo`. It seems strange to see `foo` unwrapped in `let x` assignment used in `let y` expression.

---

<div class="post-metadata">

**Author:** ![allevato](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/allevato/32/385_2.png) [@allevato](https://forums.swift.org/u/allevato)\
**Post date:** [October 31, 2022, 10:17pm UTC](https://forums.swift.org/t/pitch-if-and-switch-expressions/61149/4 "2022-10-31T22:17:52Z")

</div>

Exactly, which is why I can't think of a technical reason to disallow it. But for completeness I think we should explicitly call out that they are supported, if they are intended to be.

> [@Nikita\_Leonov](#):
>
> It seems strange to see `foo` unwrapped in `let x` assignment used in `let y` expression.

Those weren't meant to be the same `foo`; I updated my snippet to use a different dummy variable.

---

<div class="post-metadata">

**Author:** ![Nikita\_Leonov](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/nikita_leonov/32/2833_2.png) [@Nikita\_Leonov](https://forums.swift.org/u/Nikita_Leonov)\
**Post date:** [October 31, 2022, 10:21pm UTC](https://forums.swift.org/t/pitch-if-and-switch-expressions/61149/5 "2022-10-31T22:21:28Z")

</div>

Thanks! This is much clear. I suspected it could be mistyped but was not completely sure.  
I agree with you `if let`s seems like a correct extension of an old use case within this proposal too.

---

<div class="post-metadata">

**Author:** ![davbeck](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/davbeck/32/32237_2.png) [@davbeck](https://forums.swift.org/u/davbeck)\
**Post date:** [October 31, 2022, 10:21pm UTC](https://forums.swift.org/t/pitch-if-and-switch-expressions/61149/6 "2022-10-31T22:21:46Z")

</div>

I am absolutely for this.

I do sometimes wonder if Going all the way with explicit return like ruby does (the last statement is returned) would benefit Swift. I’ve seen that abused in Ruby, but it often feels like Swift arbitrarily cuts off implicit returns.

---

<div class="post-metadata">

**Author:** ![Joe\_Groff](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/joe_groff/32/1929_2.png) [@Joe\_Groff](https://forums.swift.org/u/Joe_Groff)\
**Post date:** [October 31, 2022, 10:27pm UTC](https://forums.swift.org/t/pitch-if-and-switch-expressions/61149/7 "2022-10-31T22:27:27Z")

</div>

> [@davbeck](#):
>
> I am absolutely for this.
> 
> I do sometimes wonder if Going all the way with explicit return like ruby does (the last statement is returned) would benefit Swift. I’ve seen that abused in Ruby, but it often feels like Swift arbitrarily cuts off implicit returns.

In the olden days, we were wanting to avoid dealing with the subtle distinction Rust makes between `x; y; z` (evaluate and discard x and y, then return z) vs. `x; y; z;` (evaluate and discard x, y, and z, then return nothing), as well as conversely avoiding the common issue you see in Ruby of methods accidentally returning stuff they don't intend to, and that accidental return evolving into an API contract. That is maybe less of a concern for Swift now, since we have a type system (so you can't accidentally return the wrong type of thing) and we usually require result discards to be explicit like `_ = z`, an expression which itself evaluates to Void. So the remaining hazard would be that your last statement is a call to a `discardableResult` method that happens to have a return type lining up with the function's return type.

---

<div class="post-metadata">

**Author:** ![Nikita\_Leonov](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/nikita_leonov/32/2833_2.png) [@Nikita\_Leonov](https://forums.swift.org/u/Nikita_Leonov)\
**Post date:** [October 31, 2022, 10:31pm UTC](https://forums.swift.org/t/pitch-if-and-switch-expressions/61149/8 "2022-10-31T22:31:37Z")

</div>

I was so excited to see this proposal that forget to say I am for this. This matches the style of Swift, there is also a significant amount of similar applications prior art across languages communities. It also will reduce my jealousy each time where I look over the shoulder of Kotlin engineers.

---

<div class="post-metadata">

**Author:** ![jrose](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/jrose/32/408_2.png) [@jrose](https://forums.swift.org/u/jrose)\
**Post date:** [October 31, 2022, 10:38pm UTC](https://forums.swift.org/t/pitch-if-and-switch-expressions/61149/9 "2022-10-31T22:38:41Z")

</div>

> [@ben-cohen](#):
>
> An exception to this rule is if a branch either returns, throws, or traps, in which case no value for the overall expression need be produced.

How about `continue` or `break outer`? Would it work to have the same rules as `guard` instead? (“must not fall through”) I realize that there’s still a syntactic restriction that happens because some of these forms are not expressions, but besides that I can’t think of a reason not to allow `continue` and `break` (which I think are the only “imperative” statements missing).

(For completeness, `fallthrough` also seems valid, but redundant with just specifying two patterns, so I could see that going either way.)

---

<div class="post-metadata">

**Author:** ![Joe\_Groff](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/joe_groff/32/1929_2.png) [@Joe\_Groff](https://forums.swift.org/u/Joe_Groff)\
**Post date:** [October 31, 2022, 11:06pm UTC](https://forums.swift.org/t/pitch-if-and-switch-expressions/61149/10 "2022-10-31T23:06:20Z")

</div>

> [@jrose](#):
>
> > [@ben-cohen](#):
> >
> > An exception to this rule is if a branch either returns, throws, or traps, in which case no value for the overall expression need be produced.
> 
> How about `continue` or `break outer`? Would it work to have the same rules as `guard` instead? (“must not fall through”) I realize that there’s still a syntactic restriction that happens because some of these forms are not expressions, but besides that I can’t think of a reason not to allow `continue` and `break` (which I think are the only “imperative” statements missing).

More generally, could we filter out expressions that return uninhabited types like `fatalError()`, in addition to statements that have nonlocal exits?

---

<div class="post-metadata">

**Author:** ![Paul\_Cantrell](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/paul_cantrell/32/16764_2.png) [@Paul\_Cantrell](https://forums.swift.org/u/Paul_Cantrell)\
**Post date:** [October 31, 2022, 11:06pm UTC](https://forums.swift.org/t/pitch-if-and-switch-expressions/61149/11 "2022-10-31T23:06:23Z")

</div>

I am delighted to see this much-needed proposal seeing the light of day at last! It is indeed a conspicuous gap.

I have two suggestions, which I’ll post in separate messages to avoid reply confusion. First suggestion:

**Consider allowing conditionals in arbitrary expression positions when enclosed in parentheses.**

The lack of full generality of the proposal as it stands is bothersome to me.

What if we took this proposal as is for the 3 allowed cases — returns, assignments, and declaration initializers — but then allowed if / switch expressions in arbitrary other positions if and only if enclosed in parentheses? Thus:

```swift
var x = 1 + if foo { y } else { z } // not allowed

var x = 1 + (if foo { y } else { z }) // allowed

```

Thus the potentially ambiguous example in the proposal’s Future Directions section retains its original, current meaning:

```swift
VStack {
    if showButton {
        Button("Click me!", action: clicked)
    } else {
        Text("No button")
    }
    .someProperty
}

```

…because parentheses (currently illegal, thus no breakage!) would be necessary to get `someProperty` of the `Button` / `Text` values from the conditional:

```swift
VStack {
    (if showButton {
        Button("Click me!", action: clicked)
    } else {
        Text("No button")
    })
    .someProperty // One would probably format this on the same
                   // line as `)`; just making the point that
                   // the ambiguity the proposal mentions is resolved.
}

```

---

<div class="post-metadata">

**Author:** ![ben-cohen](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/ben-cohen/32/296_2.png) [@ben-cohen](https://forums.swift.org/u/ben-cohen)\
**Post date:** [October 31, 2022, 11:13pm UTC](https://forums.swift.org/t/pitch-if-and-switch-expressions/61149/12 "2022-10-31T23:13:16Z")

</div>

> [@jrose](#):
>
> How about `continue` or `break outer`?

I agree in principle but the consequences on things like DI, and the potential that this increases the implementation time significantly, make me nervous and it would be a real shame if these niche cases ended up deferring this feature significantly.

This pitch is particularly targeting a most-useful subset, with the intention of continuing to expand the scope in later proposals. So it's tempting to put these in this bucket.

---

<div class="post-metadata">

**Author:** ![Paul\_Cantrell](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/paul_cantrell/32/16764_2.png) [@Paul\_Cantrell](https://forums.swift.org/u/Paul_Cantrell)\
**Post date:** [October 31, 2022, 11:13pm UTC](https://forums.swift.org/t/pitch-if-and-switch-expressions/61149/13 "2022-10-31T23:13:43Z")

</div>

Second suggestion: **Consider stealing Java’s solution to multi-statement branches.**

Java’s switch expressions introduce a new keyword that essentially means “return from enclosing switch,” and it’s…not too bad:

```swift
var response = switch (utterance) {
    case "thank you" -> "you’re welcome";
    case "atchoo" -> "gesundheit";
    case "fire!" -> {
        log.warn("fire detected");
        yield "everybody out!"; // yield = value of multi-statement branch
    };
    default -> "who knows";
}

```

I’m not sure `yield` is a good keyword choice for Swift, since it smells like a language that could one day have coroutines, but the introduction of a new keyword that is not `return` for this purpose seems like a promising direction.

---

<div class="post-metadata">

**Author:** ![Nobody1707](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/nobody1707/32/2169_2.png) [@Nobody1707](https://forums.swift.org/u/Nobody1707)\
**Post date:** [October 31, 2022, 11:16pm UTC](https://forums.swift.org/t/pitch-if-and-switch-expressions/61149/14 "2022-10-31T23:16:22Z")

</div>

This sounds great to me. Switch expressions cover most of my use cases by themselves, if expressions are just icing on the cake. +1

I'm also interested in the future direction of treating the last statement of a block as the value of an expression .

---

<div class="post-metadata">

**Author:** ![aheze](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/aheze/32/24200_2.png) [@aheze](https://forums.swift.org/u/aheze)\
**Post date:** [October 31, 2022, 11:51pm UTC](https://forums.swift.org/t/pitch-if-and-switch-expressions/61149/15 "2022-10-31T23:51:16Z")

</div>

Would be super nice. Would there be support for inline expressions? Since people seem to like complicated ternary expression stuff in SwiftUI views. Also, any ideas on how the formatting would look?

```swift
/// Current
struct ContentView: View {
    @State var isOn = false

    var body: some View {
        Toggle(
            isOn ? "ON" : "OFF",
            isOn: $isOn
        )
    }
}

/// New
struct ContentView: View {
    @State var isOn = false

    var body: some View {
        Toggle(
            if isOn { /// would this work?
                "ON"
            } else {
                "OFF"
            },
            isOn: $isOn
        )
    }
}

```

---

<div class="post-metadata">

**Author:** ![Jumhyn](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/jumhyn/32/3891_2.png) [@Jumhyn](https://forums.swift.org/u/Jumhyn)\
**Post date:** [October 31, 2022, 11:51pm UTC](https://forums.swift.org/t/pitch-if-and-switch-expressions/61149/16 "2022-10-31T23:51:26Z")

</div>

Glad to see some motion on this, it's definitely felt like a missing feature to me for some time. I agree existing alternatives are unsatisfactory.

I don't _love_ some of the proposed limitations, though. In order:

> [@ben-cohen](#):
>
> `if` and `switch` statements will be usable as expressions, for the purpose of:
> 
> - Returning values from functions, properties, and closures (either with implicit or explicit `return`);
> - Assigning values to variables; and
> - Declaring variables.

I appreciate the reasoning discussed in the **Future directions** section, but I worry that this is a sign that we're painting ourselves into a corner. I agree that the contrived examples are probably not worth worrying too much about, but if we adopt the pitch as written and then later find ourselves wanting to expand `if`/`switch` expressions to more positions, such a proposal will have to rise past the bar of a source break.

The alternative I see would be to adopt a different syntax for `if`/`switch` expressions. Along with the single-expression requirement, this might actually be good (since it could give a way to drop the braces, which IMO naturally suggest 'multi-statement'. E.g.,

```swift
if isRoot && (count == 0 || !willExpand) -> ""
else if count == 0 -> "- "
else if maxDepth <= 0 -> "▹ "
else -> "▿ "

```

At the very least, could we additionally allow `if`/`switch` expressions as the recursive single-expression of an outer `if`/`switch` expression branch?

> [@ben-cohen](#):
>
> **Each branch of the `if`, or each `case` of the `switch`, must be a single expression.**

Totally on board, this feels like the right design for this in Swift, at least for the initial introduction of this feature.

> [@ben-cohen](#):
>
> **Each of those expressions, when type checked independently, must produce the same type.**

This feels a bit overly restrictive to me, and I expect will be annoying in practice. The performance issues are a bummer. I'd really want this to type-check just like a ternary. I'm fine with leaving this as a future possibility though.

> [@ben-cohen](#):
>
> **The expression is not part of a result builder**

Yeah, I get why this has to be prohibited, but it's sort of unfortunate that this would also break with the ternary operator. If we used a different syntax for the expression forms, this wouldn't be necessary.

---

<div class="post-metadata">

**Author:** ![John\_McCall](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/john_mccall/32/1351_2.png) [@John\_McCall](https://forums.swift.org/u/John_McCall)\
**Post date:** [November 1, 2022, 12:05am UTC](https://forums.swift.org/t/pitch-if-and-switch-expressions/61149/17 "2022-11-01T00:05:18Z")

</div>

Is there a reason the result-builder restriction isn't just that we'll prefer the statement interpretation (and thus apply the result-builder transform) over a possible expression interpretation when `if` and `switch` appear in statement positions? It feels super weird to say that `let x = if b { "Hello" } else { "World" }` is disallowed in result builders.

Also, I agree with others that this is so far down the path of final expressions being returned as a general rule that we might as well just do that.

---

<div class="post-metadata">

**Author:** ![ben-cohen](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/ben-cohen/32/296_2.png) [@ben-cohen](https://forums.swift.org/u/ben-cohen)\
**Post date:** [November 1, 2022, 12:18am UTC](https://forums.swift.org/t/pitch-if-and-switch-expressions/61149/18 "2022-11-01T00:18:45Z")

</div>

> [@Jumhyn](#):
>
> if we adopt the pitch as written and then later find ourselves wanting to expand `if`/`switch` expressions to more positions, such a proposal will have to rise past the bar of a source break.

Is that true? I'm not sure if adding the ability to have these cases today makes adding e.g. subexpressions later more source breaking.

> [@Jumhyn](#):
>
> This feels a bit overly restrictive to me, and I expect will be annoying in practice. The performance issues are a bummer

Not just performance. Honestly, our expression inference rules lead to some truly astonishing results sometimes, and I feel like bringing that baggage to larger expressions with control flow would be a highly questionable move even if performance were not an issue.

> [@Jumhyn](#):
>
> The alternative I see would be to adopt a different syntax for `if`/`switch` expressions.

With the one case I'm aware of (the result builder one – maybe we'll find more but they'll be obscure), the source breaks are not really the main motivator for keeping the scope narrow. We could always hold back just the source breaking cases under a language variant. The narrow scope is more about timelines and feeling our way towards the right landing point incrementally.

So I don't see "give this a new syntax" as an alternative to the limitations. An alternative to this proposal full stop, sure. My feeling is there is no real merit to complicating the language by introducing a new syntax. This is to some extent already something that was already litigated during [SE-0255](https://github.com/apple/swift-evolution/blob/master/proposals/0255-omit-return.md), where an alternative returnless function syntax of `func f() -> Int = 42` was proposed, but ultimately rejected as not an improvement over the existing implicit return syntax.

---

<div class="post-metadata">

**Author:** ![ben-cohen](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/ben-cohen/32/296_2.png) [@ben-cohen](https://forums.swift.org/u/ben-cohen)\
**Post date:** [November 1, 2022, 12:20am UTC](https://forums.swift.org/t/pitch-if-and-switch-expressions/61149/19 "2022-11-01T00:20:47Z")

</div>

> [@John\_McCall](#):
>
> Also, I agree with others that this is so far down the path of final expressions being returned as a general rule that we might as well just do that.

Like I say in the pitch, maybe that's the right move, but it feels orthogonal to this proposal since the same situation occurs with implicit returns from functions etc. and so should be proposed independently. This would just increase the case for it.

---

<div class="post-metadata">

**Author:** ![ben-cohen](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/ben-cohen/32/296_2.png) [@ben-cohen](https://forums.swift.org/u/ben-cohen)\
**Post date:** [November 1, 2022, 12:24am UTC](https://forums.swift.org/t/pitch-if-and-switch-expressions/61149/20 "2022-11-01T00:24:38Z")

</div>

> [@allevato](#):
>
> I can't immediately think of a technical/conceptual reason we _wouldn't_ allow [if let bindings].

Same.

[Next page](https://forums.swift.org/t/pitch-if-and-switch-expressions/61149.md?page=2)
