[Pitch] Introduce `let-else` syntax as alternative for single expression `guard-let-else`

That's yet another interesting use case of case :smiley:
However I feel that's nothing but just making codes more verbose needlessly, additionally, that doesn't solve the question:

Because that's:

guard
    let key = getKey(),
    case let path = getPath(key: key), // Needles use of `guard` here
    let data = try? getData(path: path) else {
    ... // Additionally you lose precise error/return control
}

Moreover you cannot separate them:

guard let key = getKey() else { ... }

// Warning: 'guard' condition is always true, body is unreachable
guard case let path = getPath(key: key) else { ... } 

guard let data = try? getData(path: path) else { ... }

Then I think the below is just better.

guard let key = getKey() else ...
let path = getPath(key: key)
guard let data = try? getData(path: path) else ...

Or maybe this is just how you format them:

guard let key = getKey() else {
    ...
}
let path = getPath(key: key)

guard let data = try? getData(path: path) else {
    ...
}
guard
let key = getKey() else { ... }
let path = getPath(key: key)

guard
let data = try? getData(path: path) else { ... }

Agreed. The example case feels contrived to intersperse the guards with unrelated declarations. I tend to treat guards like preconditions--i.e., define everything required up front before continuing.

Maybe there's a better example out there for which the (clunky to my eyes) case let syntax that @bzamayo mentions is a current solution. But if that's the case, I think it merits a new pitch.

1 Like

Thanks, I like this suggestion overall.

Honestly based on this idea, though, why even have guard anymore at all?

Like... why can't we just always assume a guard is happening anytime you follow a declaration with else?

Why can't we:

`do`: do {
    var buffer: [UInt8] = []
    var length = 6
    let fooo = "β€’ΓŸΒͺΒΆβˆ‚Ζ’β€’ΓŸΓΈβˆ‚Ζ’"
    `dont`: do {
        var rageBuffer: Range<String.Index> = .init(uncheckedBounds: (lower: .init(utf16Offset: 0, in: fooo), upper: .init(utf16Offset: 7, in: fooo)))
        let foo = URL(string: "β€’ΓŸΒͺΒΆβˆ‚Ζ’β€’ΓŸΓΈβˆ‚Ζ’"), 
        let bar: Int = (
            { () -> Int? in
                Int( { () -> Int in
                    return { (_0: URL) -> Int? in 
                        _0.absoluteString.getBytes(&buffer, maxLength: 6, usedLength: &length, encoding: .windowsCP1252, range: rageBuffer,  remaining: &rageBuffer) 
                        return Data(buffer)
                            .hashValue
                            .distance(to: 42) as? Int 
                    }(foo) as? Int ?? rageBuffer.customMirror.children.count  }()   )      }()   ), 
            let qux = String(data: Data(bar as? [UInt8] ?? [0xBD, 0xFD]), encoding: .utf8)  else { break `do` }} }

I don't think we need to get rid of the guard statement for unwrapping optionals. What disturbs me is the prolific use of guard as a reverse if statement:

guard myValue > 1 else { return }
instead of
if myValue <= 1 { return }

I have to pause and more carefully read the guard statements when they're used this way which means I'm more likely to misunderstand the intent of the statement. (The above example is rather simple, but you understand my point)

There's a reason guard isn't restricted to use on optionals. It's meant to check for early out conditions. It's most helpful when one introduces a new variable into the happy path, but it's not required to do so nor should it be. guard does not mean "bail if this optional is empty", it means "bail if there's nothing more we can or should do here".

4 Likes

I don't think you read the pitch. It's not about removing guard, it's about a short alternative for a common case.