Swift: guard let vs if let
Asked Answered
O

11

204

I have been reading about Optionals in Swift, and I have seen examples where if let is used to check if an Optional holds a value, and in case it does – do something with the unwrapped value.

However, I have seen that in Swift 2.0 the keyword guard let is used mostly. I wonder whether if let has been removed from Swift 2.0 or if it still possible to be used.

Should I change my programs that contain if let to guard let?

Oversubscribe answered 27/8, 2015 at 18:23 Comment(0)
S
241

if let and guard let serve similar, but distinct purposes.

The "else" case of guard must exit the current scope. Generally that means it must call return or abort the program. guard is used to provide early return without requiring nesting of the rest of the function.

if let nests its scope, and does not require anything special of it. It can return or not.

In general, if the if-let block was going to be the rest of the function, or its else clause would have a return or abort in it, then you should be using guard instead. This often means (at least in my experience), when in doubt, guard is usually the better answer. But there are plenty of situations where if let still is appropriate.

Santossantosdumont answered 27/8, 2015 at 18:28 Comment(6)
Use if let when the non-nil case is valid. Use guard when the nil case represents some sort of error.Eulaheulalee
@Eulaheulalee I disagree with that. There are many cases where guard is appropriate even if there is no error. Sometimes it just means there's nothing to do. For example, a positionTitle method might guard if let title = title else {return}. Title may be optional, in which case this isn't an error. But guard let is still appropriate.Santossantosdumont
Yeah; I meant guard let in the comment.Santossantosdumont
in other words, "guard let" is used when the code is 99% sure of not using the else conditional; in the other hand, "if let" when the code is 50 - 50(example) to use else condition.Vicarial
The variable bound by if let is only visible inside if let scope. The variable bound by guard let is visible afterwards. So it makes sense to use guard to bind optional values also.Urinalysis
Something like if ! let would have been just as good without having us to wrap our heads around this guard thing. Saves one special word. I often think Swift is badly over-engineered.Aspirin
B
159

Guard can improve clarity

When you use guard you have a much higher expectancy for the guard to succeed and it's somewhat important that if it doesn't succeed, then you just want to exit scope early. Like you guard to see if a file/image exists, if an array isEmpty or not.

func icon() -> UIImage {
    guard let image = UIImage(named: "Photo") else {
        return UIImage(named: "Default")! //This is your fallback
    }
    return image //-----------------you're always expecting/hoping this to happen
}

If you write the above code with if-let it conveys to the reading developer that it's more of a 50-50. But if you use guard you add clarity to your code and it implies I expect this to work 95% of the time...if it ever failed, I don't know why it would; it's very unlikely...but then just use this default image instead or perhaps just assert with a meaningful message describing what went wrong!

  • Avoid guards when they create side effects, guards are to be used as a natural flow. Avoid guards when else clauses introduce side effects. Guards establish required conditions for code to execute properly, offering early exit

  • When you perform significant computation in the positive branch, refactor from if to a guard statement and returns the fallback value in the else clause

From: Erica Sadun's Swift Style book

Also as a result of the above suggestions and clean code, it's more likely you will want/need to add assertions into failed guard statements, it just improves readability and makes it clear to other developers what you were expecting.

guard​ ​let​ image = ​UIImage​(named: selectedImageName) else { // YESSSSSS
     assertionFailure(​"Missing ​​\(​selectedImageName​)​​ asset"​) 
     return
} 

guard​ ​let​ image = ​UIImage​(named: selectedImageName) else { // NOOOOOOO
​     ​return 
}

From: Erica Sadun's Swift Style book + some modifications

(you won't use asserts/preconditions for if-lets. It just doesn't seem right)

Using guards also help you improve clarity by avoiding pyramid of doom. See Nitin's answer.

Guard keeps code that handles a violated requirement next to the requirement

To be clear, guard isn't always about success vs failure. The more generic way to see it is about handling a violated requirement vs process code that isn't violated.

example:

func getImage(completion: (image: Image)? -> Void) {
   guard cache["image1"] == false else {
      completion(cache["image1"]!)
   }
   downloadAndStore("image1") { image in 
       completion(image)
   }
}

In the above the requirement is for the image to not be present in cache. If the image is present then our requirement is violated. We return early. As you can see we also handle the violated code path, right next to its requirement i.e. the structure is not:

if requirement {
   . 
   . 
   ten lines of code
   . 
   . 
} else {
   handle requirement
}

The Swift Docs on Control Flow explain the idea behind that:

Using a guard statement for requirements improves the readability of your code, compared to doing the same check with an if statement.

  • It lets you write the code that’s typically executed without wrapping it in an else block
  • it lets you keep the code that handles a violated requirement next to the requirement.

Guard avoids nesting by creating a new variable in the current scope

There is one important difference that I believe no one has explained well.

Both guard let and if let unwrap the variable however

With guard let you are creating a new variable that will exist in the current scope.

With if let you’re only creating a new variable inside the code block.

guard let:

func someFunc(blog: String?) {
    
    guard let blogName = blog else {
        print("some ErrorMessage")
        print(blogName) // will create an error Because blogName isn't defined yet
        return
    }
    print(blogName) // You can access it here ie AFTER the guard statement!!
    
    //And if I decided to do 'another' guard let with the same name ie 'blogName' then I would create an error!
    guard let blogName = blog else { // errorLine: Definition Conflicts with previous value.
        print(" Some errorMessage")
        return
    }
    print(blogName)
}

if-let:

func someFunc(blog: String?) {
    
    
    if let blogName1 = blog {
        print(blogName1) // You can only access it inside the code block. Outside code block it doesn't exist!
    }
    if let blogName1 = blog { // No Error at this line! Because blogName only exists inside the code block ie {}
        print(blogName1)
    }
}

For more info on if let do see: Why redeclaration of optional binding doesn't create an error


Guard requires scope exiting

(Also mentioned in Rob Napier's answer) :

You MUST have guard defined inside a func. It's major purpose is to abort/return/exit scope, if a condition isn't met:

var str : String?

guard let blogName1 = str else {
    print("some error")
    return // Error: Return invalid outside of a func
}
print (blogName1)

For if let you don't need to have it inside any func:

var str : String?    
if let blogName1 = str {
   print(blogName1) // You don't get any errors!
}

guard vs if

It's worth noting that it's more appropriate to see this question as guard let vs if let and guard vs if.

A standalone if doesn't do any unwrapping, neither does a standalone guard. See example below. It doesn't exit early if a value is nil. There are NO optional values. It just exits early if a condition isn't met.

let array = ["a", "b", "c"]
func subscript(at index: Int) -> String?{
   guard index > 0, index < array.count  else { return nil} // exit early with bad index
   return array[index]
}
Barb answered 1/9, 2016 at 5:21 Comment(0)
D
58

When to use if-let and when to use guard is often a question of style.

Say you have func collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int and an optional array of items (var optionalArray: [SomeType]?), and you need to return either 0 if the array is nil (not-set) or the count if the array has a value (is set).

You could implement it like this using if-let:

func collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int
    {
        if let array = optionalArray {
            return array.count
        }
        return 0
    }

or like this using guard:

func collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int
    {
        guard let array = optionalArray else {
            return 0
        }
        return array.count
    }

The examples are functionally identical.

Where guard really shines is when you have a task like validating data, and you want the function to fail early if anything is wrong.

Instead of nesting a bunch of if-lets as you get closer to finishing validation, the "success path" and the now successfully bound optionals are all in the main scope of the method, because the failure paths have all returned already.

Darelldarelle answered 1/9, 2015 at 9:15 Comment(4)
Concise and clearLinoleum
I get the idea, but we would generally just do return optionalArray?.count ?? 0, with no if let or guard let necessary.Astigmatism
@Astigmatism this is a simplified example with one condition. When there are multiple conditions you can do a gauntlet of guard condition after guard condition, rejecting the invalid cases and only letting the valid case get through to the final return statement. This should make for clearer code than a bunch of nested if statements.Darelldarelle
Agreed. No offense intended. Just an observation for future readers.Astigmatism
C
41

I'll try to explain the usefulness of guard statements with some (unoptimized) code.

You have a UI where you are validating text fields for user registration with first name, last name, email, phone and password.

If any textField is not containing valid text, it should make that field firstResponder.

here is the unoptimized code:

//pyramid of doom

func validateFieldsAndContinueRegistration() {
    if let firstNameString = firstName.text where firstNameString.characters.count > 0{
        if let lastNameString = lastName.text where lastNameString.characters.count > 0{
            if let emailString = email.text where emailString.characters.count > 3 && emailString.containsString("@") && emailString.containsString(".") {
                if let passwordString = password.text where passwordString.characters.count > 7{
                    // all text fields have valid text
                    let accountModel = AccountModel()
                    accountModel.firstName = firstNameString
                    accountModel.lastName = lastNameString
                    accountModel.email = emailString
                    accountModel.password = passwordString
                    APIHandler.sharedInstance.registerUser(accountModel)
                } else {
                    password.becomeFirstResponder()
                }
            } else {
                email.becomeFirstResponder()
            }
        } else {
            lastName.becomeFirstResponder()
        }
    } else {
        firstName.becomeFirstResponder()
    }
}

You can see above, that all the strings (firstNameString, lastNameString etc) are accessible only within the scope of the if statement. so it creates this "pyramid of doom" and has many issues with it, including readability and ease of moving things around (if the fields' order is altered, you have to rewrite most of this code)

With the guard statement (in the code below), you can see that these strings are available outside the {} and are made use of, if all fields are valid.

// guard let no pyramid of doom
func validateFieldsAndContinueRegistration() {
    
guard let firstNameString = firstName.text, firstNameString.characters.count > 0 else {
            firstName.becomeFirstResponder()
            return
        }
guard let lastNameString = lastName.text, lastNameString.characters.count > 0 else {
            lastName.becomeFirstResponder()
            return
        }
guard let emailString = email.text, 
        emailString.characters.count > 3,
        emailString.containsString("@"),
        emailString.containsString(".") else {
            email.becomeFirstResponder()
            return
        }
guard let passwordString = password.text, passwordString.characters.count > 7 else {
            password.becomeFirstResponder()
            return
        }

// all text fields have valid text
    let accountModel = AccountModel()
    accountModel.firstName = firstNameString
    accountModel.lastName = lastNameString
    accountModel.email = emailString
    accountModel.password = passwordString
    APIHandler.sharedInstance.registerUser(accountModel)
}

If the order of the fields changes, just move respective lines of code up or down, and you are good to go.

This is a very simple explanation and a use case. Hope this helps!

Edits:

2023: Code updated for newer swift syntax without using guard-let-where

Christinchristina answered 13/7, 2016 at 15:58 Comment(2)
I know I'm not supposed to say thank you on here but THANK YOU. This really helped me to understand guards!Crucify
the main answer was probably during the days of Swift 2. Updated! if anyone's curious see this explanation: https://mcmap.net/q/129314/-swift-guard-let-and-where-the-priorityChristinchristina
J
23

Basic Difference

Guard let

  1. Early exist process from the scope
  2. Require scope existing like return, throw etc.
  3. Create a new variable that can be accessed outside the scope.

if let

  1. Can not access outside the scope.
  2. no need for return statement. But we can write

NOTE: Both are used to unwrap the Optional variable.

Jermaine answered 1/8, 2017 at 4:47 Comment(0)
B
7

The clearest explanation I saw was in the Github Swift Style Guide:

if adds a level of depth:

if n.isNumber {
    // Use n here
} else {
    return
}

guard doesn't:

guard n.isNumber else {
    return
}
// Use n here
Brazilein answered 10/8, 2017 at 17:0 Comment(0)
N
7

guard let vs if let

func anyValue(_ value:String?) -> String {
    
    guard let string = value else {
        return ""
    }
    
    return string
}

func anyValue(_ value:String?) -> String {
    
    if let string = value {
        return string
    }
    
    return ""
}
Needlewoman answered 8/9, 2020 at 11:42 Comment(1)
This trivial example is better written without either if let or guard let. Just write return value ?? "".Huntingdonshire
A
5

guard

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

  • 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.

A guard statement has the following form:

guard condition else {
    //Generally return
}

if let

  • Also popular as optional binding.
  • For accessing optional object we use if let.
if let roomCount = optionalValue {
    print("roomCount available")
} else {
    print("roomCount is nil")
}
Amias answered 29/12, 2017 at 4:22 Comment(0)
D
1

I learnt this from swift with Bob..

Typical Else-If

 func checkDrinkingAge() {
      let canDrink = true

     if canDrink {
        print("You may enter")
       // More Code
        // More Code
      // More Code

         } else {
         // More Code
    // More Code
    // More Code
    print("Let me take you to the jail")
          }
     }

Issues with Else-If

  1. Nested brackets
  2. Have to read every line to spot the error message

Guard Statement A guard block only runs if the condition is false, and it will exit out of the function through return. If the condition is true, Swift ignores the guard block. It provides an early exit and fewer brackets.+

func checkDrinkProgram() {
       let iCanDrink = true

           guard iCanDrink else {
        // if iCanDrink == false, run this block
         print("Let's me take you to the jail")
          return
        }

         print("You may drink")
           // You may move on
                  // Come on.
                 // You may leave
                // You don't need to read this.
                 // Only one bracket on the bottom: feeling zen.
       }

Unwrap Optionals with Else-If

A guard statement is not only useful for replacing a typical conditional block with an else-if statement, but also great for unwrapping optionals by minimizing the number of brackets. To compare, let's first begin how to unwrap multiple optionals with else-if. First, let us create three optionals that will be unwrapped.

var publicName: String? = "Bob Lee"
var publicPhoto: String? = "Bob's Face"
var publicAge: Int? = nil

The Worst Nightmare

func unwrapOneByOne() {
         if let name = publicName {
              if let photo = publicPhoto {
                     if let age = publicAge {
                        print("Bob: \(name), \(photo), \(age)")
                                  } else {
                          print("age is mising")
                           }
                  } else {
                      print("photo is missing")
                         }
                  } else {
                        print("name is missing")
                         }
                  }

The code above certainly works but violates the DRY principle. It's atrocious. Let us break it down.+

Slightly Better The code below is more readable than above.+

func unwrapBetter() {
         if let name = publicName {
       print("Yes name")
                   } else {
               print("No name")
        return
      }

         if let photo = publicPhoto {
             print("Yes photo")
            } else {
           print("No photo")
       return
             }

        if let age = publicAge {
            print("Yes age")
                      } else {
                print("No age")
            return
                           }
     }

Unwrap with Guard The else-if statements can be replaced with guard.+

 func unwrapOneByOneWithGuard() {
             guard let name = publicName else {
                  print("Name missing")
              return
                                        }

              guard let photo = publicPhoto else {
              print("Photo missing")
                return
                                            }

                  guard let age = publicAge else {
                   print("Age missing")
                                     return
                                                 }
                 print(name)
                 print(photo)
                 print(age)
         }

Unwrap Multiple Optionals with Else-If So far, you've been unwrapping optionals one by one. Swift allows us to unwrap multiple optionals at once. If one of them contains nil, it will execute the else block.

func unwrap() {
  if let name = publicName, let photo = publicPhoto, let age = publicAge {
    print("Your name is \(name). I see your face right here, \(photo), you are \(age)")
  } else {
    // if any one of those is missing
    print("Something is missing")
  }
}

Be aware that when you unwrap multiple optionals at once, you can't identify which contains nil

Unwrap Multiple Optionals with Guard Of course, we should use guard over else-if.+

func unwrapWithGuard() {
  guard let name = publicName, let photo = publicPhoto, let age = publicAge else {
    // if one or two of the variables contain "nil"
    print("Something is missing")
    return
  }

  print("Your name is \(name). I see your, \(photo). You are \(age).")
  // Animation Logic
  // Networking
  // More Code, but still zen
}
Dedradedric answered 1/4, 2018 at 20:47 Comment(1)
please go back and fix your code formatting / indentation!Gave
O
0

The main difference between guard and if statements in swift are:

  • The if statement is used to run code when a condition is met.
  • The guard statement is used to run code when a condition is not met.
Onomatopoeia answered 20/10, 2022 at 14:46 Comment(0)
A
0

In answer to the question, if let is still supported.

But guard let is intended for cleaner code to handle the “early exit” scenario. This is especially useful where the else clause is often just a few lines to handle the “I don’t need to proceed” logic. The rest of the function can then focus on the “happy path” having gotten the assumptions/prerequisites behind you. As the The Swift Programming Language says:

Early exit

A guard statement, like an if statement, executes statements depending on the Boolean value of an expression. You use a guard statement to require that a condition must be true in order for the code after the guard statement to be executed. Unlike an if statement, a guard statement always has an else clause — the code inside the else clause is executed if the condition isn’t true.

func greet(person: [String: String]) {
    guard let name = person["name"] else {
        return
    }

    print("Hello \(name)!")

    guard let location = person["location"] else {
        print("I hope the weather is nice near you.")
        return
    }

    print("I hope the weather is nice in \(location).")
}

greet(person: ["name": "John"])
// Prints "Hello John!"
// Prints "I hope the weather is nice near you."
greet(person: ["name": "Jane", "location": "Cupertino"])
// Prints "Hello Jane!"
// Prints "I hope the weather is nice in Cupertino."

If the guard statement’s condition is met, code execution continues after the guard statement’s closing brace. Any variables or constants that were assigned values using an optional binding as part of the condition are available for the rest of the code block that the guard statement appears in.

If that condition isn’t met, the code inside the else branch is executed. That branch must transfer control to exit the code block in which the guard statement appears. It can do this with a control transfer statement such as return, break, continue, or throw, or it can call a function or method that doesn’t return, such as fatalError(_:file:line:).

Using a guard statement for requirements improves the readability of your code, compared to doing the same check with an if statement. It lets you write the code that’s typically executed without wrapping it in an else block, and it lets you keep the code that handles a violated requirement next to the requirement.

In short, guard offers the following early exit benefits:

  1. It keeps the “test” and the “failure handling code” right next to each other, making it easier to reason about our code.

  2. The compiler will warn you if you accidentally neglect to return (or throw or some other type of exit) inside the else clause.

  3. It eliminates a lot of nested braces that you can sometimes have if you have one or more if clauses for what is often the standard flow within the function.

But where appropriate, feel free to use if let. Both guard let and if let are supported. For more information, see the control flow section of The Swift Programming Language.

Astigmatism answered 20/11, 2023 at 1:12 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.