Skip to content

Comments

FLIP 355: Cadence Guard Statement#356

Open
turbolent wants to merge 2 commits intomainfrom
bastian/cadence-guard-statement
Open

FLIP 355: Cadence Guard Statement#356
turbolent wants to merge 2 commits intomainfrom
bastian/cadence-guard-statement

Conversation

@turbolent
Copy link
Member

Work towards #355

@bluesign
Copy link
Collaborator

bluesign commented Feb 6, 2026

// With guard: clean and safe
fun processUser(name: String?, age: Int?): User? {
    guard let n = name else { return nil }
    guard let a = age else { return nil }

    return User(name: n, age: a)
}

I like it in general but syntax feels strange to me. But probably it is just me

edit: also piqued my interest // must exit: return, panic, break, or continue why it is like this?

@janezpodhostnik
Copy link
Contributor

I like it, but the guard keyword sounds just a bit off...

How about syntax like this:

if not let a = input.a { return nil;}

// the reasoning is maybe we would want something like this:
if let b = input.b && let c = input.c { //... }

// and then this:
if not let d = input.d && let e = input.e { //... }

@turbolent
Copy link
Member Author

I don't think we should try to come up with new syntax just for the sake of it, especially if there is already an established one we can borrow.

The Swift evolution's commonly rejected proposals has a section regarding guard, and how it is not just a negated if/unless/if not: https://github.com/swiftlang/swift-evolution/blob/main/commonly_proposed.md#control-flow-closures-optional-binding-and-error-handling

@zhangchiqing
Copy link
Member

I see three equivalent styles here for handling optionals:

  1. if let chaining (original idiomatic style)
fun processUser(name: String?, age: Int?): User? {
    if let n = name {
        if let a = age {
            return User(name: n, age: a)
        }
    }
    return nil
}
  • Fewer concepts to learn
  • Some nesting / rightward drift as more optionals are added
  1. Explicit nil checks + forced unwrap
fun processUser(name: String?, age: Int?): User? {
    if name == nil || age == nil {
        return nil
    }

    return User(
        name: name!,
        age: age!
    )
}
  • More verbose
  • Very explicit control flow
  • Easy to reason about for readers unfamiliar with optional binding (Note, User(name: name, age: age) will remind them about the mistake)
  1. guard let (new syntax)
fun processUser(name: String?, age: Int?): User? {
    guard let n = name else { return nil }
    guard let a = age else { return nil }

    return User(name: n, age: a)
}
  • Flattens control flow
  • Reads linearly with explicit early-exit
  • Requires learning a new construct

From my understanding, all three forms are semantically equivalent and compile down to the same short-circuiting logic.

Question:
Does guard let provide any meaningful performance or compilation advantages over if let or explicit nil checks, or is its primary for readability?

@turbolent
Copy link
Member Author

@bluesign

also piqued my interest // must exit: return, panic, break, or continue why it is like this?

Because that's the very feature of it: The else branch is the unhappy case and diverges, and must exit, so that the main branch can always assume that the condition held.

@zhangchiqing

Does guard let provide any meaningful performance or compilation advantages over if let or explicit nil checks, or is its primary for readability?

No, it does not have any performance/compilation advantages, its primary benefit is readability and safety.

@turbolent
Copy link
Member Author

turbolent commented Feb 6, 2026

Some additional context: We already have a form of guard let that is commonly used and I did not mention in the proposal: nil coalescing, e.g. let value = opt ?? panic("..."). However, this only allows panics and not e.g. a return from the function, and does not allow additional code/statements, e.g. emitting an event.

Another related language is Kotlin, which has the "elvis" operator ?:, which is similar to ?? in Cadence, and allows control-flow on the right-hand side, including return, e.g. val value = opt ?: return.

Copy link
Member

@jordanschalm jordanschalm left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for writing this up. I think this feature would be helpful for managing optionals in larger functions.

@bluesign
Copy link
Collaborator

bluesign commented Feb 6, 2026

Because that's the very feature of it: The else branch is the unhappy case and diverges, and must exit, so that the main branch can always assume that the condition held.

@turbolent so this will be some kind of pre-condition but also will not fail ?

better examples can be constructed with failable cast or optional returning functions I guess.

guard let vault <- r as? @{FungibleToken.Vault} else {
   return nil
}
vault.withdraw(....)
guard let flowVaultReference = acc.storage.borrow<&Flowtoken.Vault>(from:/storage/flowTokenVault>() else {
  return nil
}

I think this is a bit shift in how we write Cadence also. ( in my opinion little conflicting with pre-conditions ) Like functions should return optionals instead of failing.

Also there are cases like this:

	var listed = false
	if let marketV3CollectionRef = acct.capabilities.borrow<&TopShotMarketV3.SaleCollection>(/public/topshotSalev3Collection) {

		let salePrice = marketV3CollectionRef.getPrice(tokenID: UInt64(5188888))

		if salePrice != nil {
			listed = true
		}
	} else if let marketV1CollectionRef = acct.capabilities.borrow<&Market.SaleCollection>(/public/topshotSaleCollection) {

		let salePrice = marketV1CollectionRef.getPrice(tokenID: UInt64(5188888))

		if salePrice != nil {
			listed = true
		}
	} 
	if listed {
		panic("moment is already listed for sale")
	}

which I think guard will not solve.

@turbolent
Copy link
Member Author

turbolent commented Feb 6, 2026

@bluesign

so this will be some kind of pre-condition but also will not fail ?

Like a pre-condition the condition must succeed, but unlike the pre-condition execution will not necessarily abort execution: the else branch may panic, which would be equivalent, but it may also just return from the function.

guard let vault <- r as? @{FungibleToken.Vault} else {
   return nil
}
vault.withdraw(....)

That's a good example! 👍

I think this is a bit shift in how we write Cadence also. ( in my opinion little conflicting with pre-conditions ) Like functions should return optionals instead of failing.

This proposal and feature do not intend to move developers away from pre-conditions or if they should write code that gracefully returns instead of aborting execution. In many cases pre-conditions are a better choice over guards, like pointed out by Jordan above (#356 (comment)).

But in cases where a pre-condition won't be possible, a guard can help, e.g. for unwrapping optionals (and then panicing or returning from the function, to be decided by the developer).

The proposal does try to recommend the early return pattern, and proposes a feature that allows expressing it elegantly.

Also there are cases like this: [...] which I think guard will not solve.

Yeah, in this example a guard would not help / be applicable.

@bluesign
Copy link
Collaborator

bluesign commented Feb 7, 2026

btw to make it clear, I like this feature (As people know me know well, I am big hater off forced unwrap operator ); now while reading my comments, it seems a bit negative, it is my general personality, always trying to find an edge case.

Copy link
Member

@SupunS SupunS left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Excellent proposal! I like the idea of having a way to early exit for "undesired" inputs, and keep the main execution path "clean". 👍

@turbolent turbolent changed the title Add FLIP for Cadence guard statement FLIP 355: Cadence Guard Statement Feb 9, 2026
Copy link
Member

@joshuahannan joshuahannan left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great addition! I'll definitely want to use this in a lot of places

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

7 participants