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 guard
s 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-let
s. 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]
}
if let
when thenon-nil
case is valid. Useguard
when thenil
case represents some sort of error. – Eulaheulalee