[FEATURE REQUEST] `guard` statement

Verse shipped with an if statement / expression, but it’s quickly becomes apparent that the languages often times suffers from the great pyramid of doom phenomenon.

if (...) {
  if (...) {
    if(...) {
      ...
    }
  }
}

Being used to Swift development I fell in love with the guard statement.

Guard Statement

A guard statement is used to transfer program control out of a scope if one or more conditions aren’t met.

A guard statement has the following form:

guard <#condition#> else {
  <#statements#>
}

The value of any condition in a guard statement must be of type Bool or a type bridged to Bool. The condition can also be an optional binding declaration, as discussed in Optional Binding.

Any constants or variables assigned a value from an optional binding declaration in a guardstatement condition can be used for the rest of the guard statement’s enclosing scope.

The else clause of a guard statement is required, and must either call a function with the Never return type or transfer program control outside the guard statement’s enclosing scope using one of the following statements:

  • return
  • break
  • continue
  • throw

Source

The guard statement allows one to inverse some control flow and keep the execution within the same scope level instead of moving into a new then branch.

Here’s a quick example from Swift:

func foo() -> Int? {
  if condition {
    if let unwrappedInt = someOptionalInt {
      if otherCondition {
        doSomeWork()
        return unwrappedInt
      }
    }
  }
  cleanUpWork()
  return nil
}

func foo() -> Int? {
  guard condition, let unwrappedInt = someOptionalInt, otherCondition else {
    cleanUpWork()
    return nil
  }
  // we escaped the if pyramid
  doSomeWork()
  return unwrappedInt
}

This example is not a question if the code can be restructured to return an int or fail, it’s just a trivial example of a possible if pyramid of doom!

I can partly achieve something similar for some use case in verse, but it’s a bit verbose.

Func(): void = {
  Value := if (UnwrappedValue := OptionalValue?) { 
    UnwrappedValue 
  } else {
    return
  }

  Something := if (UnwrappedThing := OptionalThing?) { 
    UnwrappedThing 
  } else {
    return
  }

  # use `Value` and `Something` from here
}

It would be great if verse gained a native solution for a guard statement (and possibly an expression?).

2 Likes

We’ve discussed a guard expression for years internally at Epic and the consensus was generally to see what the community thought about it - so I’m glad you bring it up.

The more examples pyramids of doom that the community can supply of real and not just pseudo code - the better the case for it.

3 Likes

Happy to see that it’s been discussed internally. In my personal experience so far I often times have to restructure my code an split it a lot of small methods that partly swallow the nested if statements so that it does not grow the pyramid too quickly. In many cases this involves dynamic type casting, optional unwrapping etc. (shifting between agent, player, fort_character, grabbing and unwrapping optionally cached stored properties or accessing arrays and maps).

I’m not sure if the majority from the creators in the Fortnite community is familiar with this or if they just assume that deeply nested if statements is the status quo in modern programming languages. Let’s hope there are more experienced developers who would share their need for a native guard statement for Verse.

Swift’s guard statement introduces a new control flow paradigm to the mix. In Verse, there are two idioms that I think are a bit simpler and more general. An ‘if’ and ‘for’ statement’s condition may have an arbitrary number of variable bindings and tests separated by commas, semicolons, or newlines, and can be indented.

if(a):
if(b):
if(c):
.

can be written as…

if:
a
b
c
then:
something using any variables bound in a,b,c
else:
something else

Also, if you’re writing a function, and your guard’s ‘else’ just fails, you can skip the ‘if’ statements and just intermix your tests, variable bindings, and code together sequentially.