Support for returning the result of a Guard statement

I have a suggestion to allow a guard return statement. There are times when you unwrap an optional, and if there is no value, you may throw an error, and then return the value. For example:

let value: Int? = 0
guard let value else {
    throw SomeError.unavailable
}
return value

If the syntax allowed a guard return, you could instead simplify the above to:

guard return value else {
    throw SomeError.unavailable
}
5 Likes

This would be an elegant shortcut to the current syntax we have now.

This would be also valid?

guard return someValue else {
    return otherValue
}

Also, does it allow to unwrap values like so:

func myOptionalFunc() -> Int? { ... }

guard return value = myOptionalFunc() else {
    throw SomeError
}
1 Like

If we're exploring syntactic sugar, I'd favor the following:

return value ?? throw SomeError.unavailable
13 Likes

I would be happy with this as well. It would be great to have both options - the guard's else condition does allow you to add some additional logic though, eg logging prior to throwing. However, when you don't need additional logic, your suggestion would be preferable.

func myOptionalFunc() -> Int? { ... }

guard return value = myOptionalFunc() else {
    throw SomeError
}

For this example, you would just use:

guard return myOptionalFunc() else {
    throw SomeError
}
2 Likes

This would only cover a one-liner else statement and the usage of ?? which has another meaning nowadays (left and right side must have same type)

In this particular example you could just use if instead of guard:

if let value = expression {
    return value
}
throw SomeError.unavailable
2 Likes

You can get almost this by declaring this operator:

func ?? <Wrapped>(value: Wrapped?, error: @autoclosure () -> Error) throws -> Wrapped {
    guard let value else { throw error() }
    return value
}
return try value ?? SomeError.unavailable
4 Likes

There's prior discourse on this exact feature. Seems like my desired syntax depends on making throw an expression that returns Never.

5 Likes

Or this:

func `throw`<T>(_ error: Error) throws -> T {
    throw error
}

func bar() throws -> Int {
    var value: Int? = 0
    return try value ?? `throw`(SomeError.unavailable)
}

I used the guard here to illustrate the proposal. But you are right though - you could do this. You could also shorten your example to:

if let expression {
    return expression
}
throw SomeError.unavailable

However, using a different example:

if let value = someFunction() {
    return value
}
throw SomeError.unavailable

Now you need to declare and set value just to return this. This also applies if reading an optional property on some type, eg if let value = expression?.value

It would be nice to just return it without needing to set it first:

guard return someFunction() else {
    throw SomeError.value
}
1 Like

IMHO this code structure reads as the throwing code path being the expected/default, which feels really wrong to me.

@totidev's suggested syntax is really nice and also matches the if let ... pattern nicely.

2 Likes

Same could be said about pretty everything else, no?

if let value { foo(value) }

"you need to declare and set value just to pass it as a parameter."

if let value { anotherVariable = value }

"you need to declare and set value just to assign it to another variable."

If we're exploring ideas for this, here's what I use all the time:

infix operator ?!: NilCoalescingPrecedence

public func ?! <T>(lhs: T?, rhs: @autoclosure () -> Error) throws -> T {
    
    if let value = lhs {
        return value
    }
    
    throw rhs()
}

This gives me this syntax:

self.nonOptional = try someOptional ?! SomeError.missingValue

This is one of three operators I use for dealing with optionals:

?? → use LHS if it's there, return RHS otherwise
?! → use LHS if it's there, throw RHS otherwise
!! → use LHS if it's there, crash with RHS otherwise (aka the "unwrap or die" operator)

7 Likes

That's really elegant.

I know !! was discussed a lot in the earlier days (maybe it was even implemented in very earlier versions of Swift?) and was apparently controversial.

In any case, when you lay them out together like that, it makes it so clear, and so compelling that they should be standardised (i.e. included in the standard library).

1 Like

See I had tried this:

func yeet(_ error: Error) throws -> Never { throw error }

return try value ?? yeet(SomeError())

But it gave me a compile error about using Never in that position (link). I thought the whole point of Never is that it can replace a value of any type?

This is the dream, but the core team hasn't made a decision on whether Never should be able to be used this way.

1 Like

I was somewhat indifferent to this when this topic started - and I've seen similar if not identical suggestions in years gone by - but for whatever reason it stuck in my mind these last few days, and I've realised that I would use this a lot if it were available. I really do write a lot of code which is basically:

guard let value = maybeFoo() else {
    // Logging here etc.
    throw SomeError()
}

return value

Or variations thereof (e.g. using try?).

There's no universal shorthand for this today - ?? / ?! / !! work in some cases, but not if there's more to be done in the error case before returning / throwing.

I think there's some aspects that need to be fleshed out clearly - e.g. what does the example below do - but I'm upbeat on this.

func grabFoo() -> Foo? {
    guard return maybeFoo() else {
        // Logging here etc.
        return nil
    }
}

i.e. does it consider a nil from maybeFoo to pass the guard (so the else body is actually unreachable) since the grabFoo return type is optional?

You can write:

return try maybeFoo() ?? {
  // Logging here, etc.
  throw SomeError()
}()
2 Likes

I'm personally not very fond of this solution.

Yes it's shorter. however it breaks the "meaning" of guard in my opinion.

Guards could be written with ifs but use have this word to separate the requirement validation from the actual implementation. I also see guards as lines that can be removed "without impacting" the implementation (of course the fonction is then different but only in the requirement not in the general algorithm)

Having guard return value... mixes again implementation from requirement validation, it removes 1 line of code but introduces more clunky code

2 Likes