If I’ve got a bunch of chained guard let statements, how can I diagnose which condition failed, short of breaking apart my guard let into multiple statements?
Given this example:
guard let keypath = dictionary["field"] as? String,
let rule = dictionary["rule"] as? String,
let comparator = FormFieldDisplayRuleComparator(rawValue: rule),
let value = dictionary["value"]
else
{
return nil
}
How can I tell which of the 4 let statements was the one that failed and invoked the else block?
The simplest thing I can think of is to break out the statements into 4 sequential guard else statements, but that feels wrong.
guard let keypath = dictionary["field"] as? String
else
{
print("Keypath failed to load.")
self.init()
return nil
}
guard let rule = dictionary["rule"] as? String else
{
print("Rule failed to load.")
self.init()
return nil
}
guard let comparator = FormFieldDisplayRuleComparator(rawValue: rule) else
{
print("Comparator failed to load for rawValue: \(rule)")
self.init()
return nil
}
guard let value = dictionary["value"] else
{
print("Value failed to load.")
self.init()
return nil
}
If I wanted to keep them all in one guard statement, I can think of another option. Checking for nils inside the guard statement might work:
guard let keypath = dictionary["field"] as? String,
let rule = dictionary["rule"] as? String,
let comparator = FormFieldDisplayRuleComparator(rawValue: rule),
let value = dictionary["value"]
else
{
if let keypath = keypath {} else {
print("Keypath failed to load.")
}
// ... Repeat for each let...
return nil
}
I don't even know if that will compile, but then I might as well have used a bunch of if let
statements or guard
s to begin with.
What's the idiomatic Swift way?
Very good question
I wish I had a good answer for that but I have not.
Let's begin
However let's take a look at the problem together. This is a simplified version of your function
Inside the else we don't know what did go wrong
First of all inside the
else
block we do NOT have access to the constants defined in theguard
statement. This because the compiler doesn't know which one of the clauses did fail. So it does assume the worst case scenario where the first clause did fail.Conclusion: we cannot write a "simple" check inside the
else
statement to understand what did not work.Writing a complex check inside the else
Of course we could replicate inside the
else
the logic we put insito the guard statement to find out the clause which did fail but this boilerplate code is very ugly and not easy to maintain.Beyond nil: throwing errors
So yes, we need to split the guard statement. However if we want a more detailed information about what did go wrong our
foo
function should no longer return anil
value to signal an error, it should throw an error instead.So
I am curious about what the community thinks about this.
One possible (non-idiomatic) workaround: make use of the
where
clause to track the success of each subsequent optional binding in theguard
blockI see nothing wrong with splitting up your guard statements in separate guard blocks, in case you're interested in which guard statement that fails.
Out of a technical perspective, however, one alternative to separate
guard
blocks is to make use of awhere
clause (to each optional binding) to increment a counter each time an optional binding is successful. In case a binding fails, the value of the counter can be used to track for which binding this was. E.g.:Above we make use of the fact that the result of an assignment is the empty tuple
()
, whereas the side effect is the assignment to the lhs of the expression.If you'd like to avoid introducing the mutable counter
i
prior the scope ofguard
clause, you could place the counter and the incrementing of it as a static class member, e.g.Possibly a more natural approach is to propagate the value of the counter by letting the function throw an error:
I should probably point out, however, that the above is probably closer to be categorized as a "hacky" construct, rather than an idiomatic one.
Normally, a
guard
statement doesn't let you distinguish which of its conditions wasn't satisfied. Its purpose is that when the program executes past the guard statement, you know all the variables are non-nil. But it doesn't provide any values inside the guard/else
body (you just know that the conditions weren't all satisfied).That said, if all you want to do is
print
something when one of the steps returnsnil
, you could make use of the coalescing operator??
to perform an extra action.Make a generic function that prints a message and returns
nil
:Then use this function as a "fallback" for each case. Since the
??
operator employs short-circuit evaluation, the right-hand side won't be executed unless the left-hand side has already returned nil.I think other answers here are better, but another approach is to define functions like this:
And then use it like this
You could extend it to print the file & line number of the guard statement like other answers.
On the plus side, there isn't too much clutter at the call site, and you only get output for the failing cases. But since it uses tuples and you can't write a function that operates on arbitrary tuples, you would have to define a similar method for one parameter, two parameters etc up to some arity. It also breaks the visual relation between the clause and the variable it's being bound to, especially if the unwrapped clauses are long.
Erica Sadun just wrote a good blog post on this exact topic.
Her solution was to hi-jack the
where
clause and use it to keep track of which guard statements pass. Each successful guard condition using thediagnose
method will print the file name and the line number to the console. The guard condition following the lastdiagnose
print statement is the one that failed. The solution looked like this:Erica's write-up on this topic can be found here
In my personal opinion, the Swift way shouldn't require you to check whether the values are
nil
or not.However, you could extend
Optional
to suit your needs:Allowing for: