`if let` shorthand

The seems useful. It's worth noting that switch cases don't currently support optional binding conditions. For example, this doesn't compile today:

func foo(action: String, userId: String?) {
    switch action {
        case "bar" where let userId = userId: // 🛑 error: expected expression for 'where' guard of 'case'
            bar(userId)
        default:
          break
    }
}

func bar(_ userId: String) {
    // ...
}

I'm not really sure why switch cases don't support optional binding conditions. If we added support for them in the future, we would definitely want to support the shorthand syntax as well.

3 Likes

I think this is what you want.

func foo(action: String, userId: String?) {
    switch (action, userId) {
        case ("bar", let userId?):
            bar(userId)
        // ...
        default: break
    }
}

func bar(_ userId: String) {
    // ...
}
6 Likes

Thank you! This is a really good approach and I will use it, but in my case the code gets very noisy. I have 12 cases and only two of them use 2 different optional variables that need to be unwrapped:

switch (action, foo, bar) {
case ("a", _, _): ...
case ("b", _, _): ...
case ("c", _, _): ...
case ("d", _, _): ...
...
case ("x", let foo?, _): ...
case ("y", _, let bar?): ...
}

So, it would be very handy if it were possible to do where let foo:

switch action {
case "a": ...
case "b": ...
case "c": ...
case "d": ...
...
case "x" where let foo: ...
case "y" where let bar: ...
}
1 Like

I realize it's not the exact question that you asked, but, if the action value does not absolutely have to be a string, an enum with associated values might make a good choice:

enum Action {
    case a, b, c, d
    case x(String)
    case y(Int)
}

I am guessing that foo and bar are optional in your original code because most actions don't require an additional value, so callers of those actions provide nil, but the other two actions require a value.

If that is the case, an enum with associated values ensures that the actions that require a value have the necessary value, and the other actions don't have to worry about passing nils.

The switch statement ends up very close to what you were originally wanting and since action is an enum and not a string, the compiler can check to make sure every enum case is handled:

switch action {
case .a: ...
case .b: ...
case .c: ...
case .d: ...
...
case .x(let foo): ...
case .y(let bar): ...
}

If foo and bar really could be nil, then the associated value types could be optionals.

Again, I realize what I suggest isn't exactly what you were originally looking to do, but if the type of action is in your control, modeling the actions as an enum with associated values may be a good choice for a variety of reasons.

1 Like

Great job! I was thinking that the guard statement could be also improved by an extreme short syntax:

 // inferred `return` or `return nil` in this shortened version
guard condition == true

guard let x

instead of these very common and well-known paths:

guard condition == true else { return }
guard condition == true else { return nil }

guard let x = x else { return }
guard let x = x else { return nil }

IMO the short version is very readable and easy-to-write:

func getData(with query: String) -> Data? {
   
   guard query.isEmpty == false
   guard isValidQuery(query)

   return db.perform(query)
}
3 Likes

This is a commonly rejected change.

  • Infer return for omitted guard body: It has been proposed many times to allow omission of the guard body for the sake of brevity. However, a core principle of Swift is to make control flow explicit and visible. For example, the try keyword exists solely to indicate to the human reader where thrown errors can happen. Implicit returns would violate this principle, favoring terseness over clarity in a way that isn't typical of Swift. Furthermore, there are many ways of exiting the scope other than return (loops may want break or continue ), and not every function has an obvious default value to return.

True, but until about a week ago so was a shorthand syntax for if let foo = foo. ;)

That said, I think the reasons presented as to why inferring return is commonly rejected are good ones.

8 Likes

I see the point and thanks for the link. I think however that maybe in the future it could be reconsidered. It could be applied for example only in very clear scopes, in other ones it could be the compiler to warn the user etc.

2 Likes

In my case, it was literally necessary to process strings with optional parameters. More specifically, I refactored the code a bit regarding parsing deep links. This can be solved in many ways, but in this case I did not complicate the current implementation. I needed to define enum from a string which is a component of a path and query parameters:

enum DeeplinkEvent: Equatable {
    case presentPromoCode
    // ...
    case resetPassword(verificationCode: String)

    init?(_ rawValue: String, code: String?) {
        switch rawValue:
        case "promocode": 
            self = .presentPromoCode
        // ...
        case "reset-password" where code != nil: 
            self = .resetPassword(verificationCode: code!)
        default: return nil
    }
}
1 Like

Do we now support:

if let x as? Foo {   // if let x = x as? Foo

Or is that not included?

That syntax was not included in SE-0345, but was mentioned as a potential future direction.