Guard statement for early exit / positive coding

Instead of commonly used ambiguous if statements at the start of functions that imply early exit/guards, a guard statement (a la Swift) in TypeScript would be super useful because it allows to:

  1. Check conditions early and exit if they fail, keeping your main code path clean and positive
  2. Automatically narrow types after the check, so TypeScript knows a variable is safe to use
  3. Write "happy path" code that flows forward without deep nesting or defensive checks

It's about writing code that follows the natural flow of what should happen, rather than constantly checking what might go wrong.

example:

// Before: Nested conditions obscure your intent
function processUser(user?: User) {
  if (user && user.isActive && user.permissions.includes('admin')) {
    // Finally, your actual logic here
    doSomethingImportant(user);
  }
}

// After: Guards make your main path clear
function processUser(user?: User) {
  guard (user) else return;
  guard (user.isActive) else return;
  guard (user.permissions.includes('admin')) else return;
  
  // Main logic stands out - not buried in conditionals
  doSomethingImportant(user);
}

so if (!user) { return; } etc?

1 Like

Your problem appears to be that you're relying on using an initial check that wraps your function body rather than using early returns. Your "After" example can be written using entirely using existing syntax, in a way that many authors would immediately recognize and understand:

// After: Guards make your main path clear
function processUser(user?: User) {
  if(!user) return;
  if(!user.isActive) return;
  if(!user.permissions.includes('admin')) return;
  
  // Main logic stands out - not buried in conditionals
  doSomethingImportant(user);
}

No need for new syntax. This is even less characters, if that matters.

2 Likes

This feel like—judging that you don't want to use already the existing syntax as others have pointed out—like a use case for (built-in) decorators for common checks :thinking:

@guard((user?: User) => user)
@guard((user?: User) => user.isActive)
@guard((user?: User) => user.permissions.includes('admin'))
function processUser(user?: User) {
  // Main logic stands out - not buried in conditionals
  doSomethingImportant(user);
}

or something like a schema

@checkStructure(user: { isActive: true, permissions: { $type: 'array', $contains: 'admin' } })
function processUser(user?: User) {
  // Main logic stands out - not buried in conditionals
  doSomethingImportant(user);
}

but if it doesn't need to be built-in, once the proposal gets implemented, this also can be natively implemented in userland code without new syntax

1 Like

You're missing my point. You can natively write all your code in vanilla JS, but that doesn't mean it's natural.
The decorator examples you give I think make it worse as they are even less intuitive.

The suggestion to use if(!user) statements also slightly misses the mark as they are still negative statements that break the flow. Something akin a guard function makes it explicit to do what it says on the tin - early exit.

But for lack of that, @tabatkins ' suggestion is the cleanest with existing syntax.

And you are missing the point about ECMAScript in terms of what it should and shouldn't do. I agree that guard statements are an important part of a lot of code. However, as I and others in this thread have shown, there's no urgent need to further clutter the language as this is already possible in vanilla JS today. Keep in mind, that it is not just your proposal but a lot of people who would like this or that cemented in their own syntax.

I also don't really like the decorator version - it means the guards have to be at the beginning of the function - you can't pre-compute a value, store it in a variable, then use it in a guard and such.

But, if (!...) return; works fine IMO.

The suggestion to use if(!user) statements also slightly misses the mark as they are still negative statements that break the flow.

The only reason the "guard" version isn't negative is because the "else" word is used. We could write the "if" in a similar fashion if that format is prefered. So instead of

guard (user) else return;

This is already valid today:

if (user); else return;

Or you can do this if prefered:

if (user) {} else return;