3 ms·
> Since every single language has an if statement, we clearly know what intent an if block signals. Guard has different semantics than if. For instance, I can
by skohan 6y ago
> Since every single language has an if statement, we clearly know what intent an if block signals.
Guard has different semantics than if. For instance, I can write:
if self == nil {
... do some cleanup
return
}
Or:
if self nil {
... do something else without returning
}
If it's a simple early return, then yes it's pretty easy to tell the intent. But imagine that I have to do some complex cleanup work over many lines before the return. In the case of a guard, I can look at the guard statement and immediately know that this block of code must end the control flow within this function. With an if statement, I have to read and parse the content of the block before I can understand that execution should not progress past this block.
This is especially relevant when you're maintaining code which was written by someone else. With an if statement, you might accidentally remove a return statement from an if block which is required for correctness, and you may not find out about it until you run your program (or in the worst case after you release your code, and it causes unexpected bugs in production). With the guard statement, the compiler will enforce this constraint. It's one of many tools which Swift gives you to help you write correct code.
Also, with your example:
if self == nil { return }
I assume you are alluding to languages where self would be implicitly unwrapped in this case, so it can be treated as a concrete instance rather than an optional after this statement. IMO it is a strength of swift that you don't have this magical conversion of an optional to a concrete value which never explicitly written, but is inferred by the compiler. Guard offers clarity and consistency about exactly where the unwrap happens, and it's barely more verbose than a bare if statement. IMO this is a very elegant solution, and in practice it's something I miss in languages which don't have it.
- dmitriid 6y ago> Guard has different semantics than if. Yes, it has. But why? Everyone parrots the same "different semantics" excuse never bothering to ask why. The only reason `guard` exists is to manually tell the compiler to do a nullability check. And that is it. It's a very small, very specific case wrapped into a separate syntax of its own because the compiler and the type checker are just not good enough. guard X else {} is exactly the same as if X is not null {} // pseudo code but the compiler/type checker are not good enough to know that once null checks are passed in a statement, it's safe to use that value in the code that follows. This leads to horrendous piles of useless code like the one I showed: guard let self = self else { return } How can one look at that and say, "yup, that's good design"? > But imagine that I have to do some complex cleanup work over many lines before the return. In the case of a guard, I can look at the guard statement and immediately know And immediately know almost nothing. Except that it's a very specific `if` case that returns. Which brings us to: > you might accidentally remove a return statement from an if block which is required for correctness, and you may not find out about it until you run your program (or in the worst case after you release your code, and it causes unexpected bugs in production). It won't cause issues if the language is actually properly designed. Because the variables you'll use will not checked for nullability in this case. And even Java will warn you with "potentially null value". But Swift can't do that without a `guard` statement. Go figure. > IMO it is a strength of swift that you don't have this magical conversion of an optional to a concrete value Yes, you do. You wrap it in a guard statement, assign a variable to itself, and poof, magic, it's suddenly unwrapped.
- skohan 6y ago> Everyone parrots the same "different semantics" excuse never bothering to ask why. It's like you didn't even read my comment, I gave a reasoned answer why it's different. It gives a queue to the user that the condition must be met, or else there will be an early return. The example you gave is also not equivalent - here you have: guard X else { /* branch A */ } // Branch B And here you have: if X is not null { // Branch B } // Branch A The problem with your argument is that you are taking a subjective preference - that magical unwrapping via if-statements is the best form of unwrapping - and stating it as if it is an objective fact. You haven't actually given any arguments for why guard is an inferior approach beyond basically saying: just look at it, it's bad language design. Why is it bad? > It won't cause issues if the language is actually properly designed. Because the variables you'll use will not checked for nullability in this case. And even Java will warn you with "potentially null value". But Swift can't do that without a `guard` statement. Again, it's not a question of proper/improper design or a failure of the compiler. The designers of swift made a conscious decision to provide exactly 3 ways to unwrap an optional: 1) the force unwrap `!` operator, 2) the `if let` statement, 3) the `guard let` statement Just because that is not the exact design you would prefer does not make it a bad design. You're also ignoring the uses for `guard` beyond null checking. For instance, if I wanted to check that my arguments are within a certain bounds, I could do something like this: func foo(x: Int) -> Int { guard x >= 0, x <= 42 else { return 0 } return 2*x } This has nothing to do with "manually tell[ing] the compiler to do a nullability check ... because the compiler and the type checker are just not good enough" - it's about having an explicit language construct to signal that you do not want to continue with the logic of this function if the condition is not met. > Yes, you do. You wrap it in a guard statement, assign a variable to itself, and poof, magic, it's suddenly unwrapped. These are actually very different things. In this statement: if x == nil { ... } you are taking the conditional (`x == nil`) and giving it this magic side-effect that once it's executed, a new variable of a separate type is created and will replace `x` until the end of execution. Every other conditional simply evaluates to a boolean, so why should this one special case have this side effect where it's also declaring and assigning a new variable? To contrast that with the statement which bothers you so much aesthetically: guard let self = self else { return } Here the `let <identifier> = <value>` denotes the fact that you are declaring and assigning a new variable here, which is why the type change can take place. It happens to have the same name as the previous `self`, but every aspect of this is consistent with the rules of the language, and there is no special case being created here. You may not like it, and that's fine, but your personal taste does not reflect an objective truth about language design. Tens of thousands of swift developers around the world are able to understand the motivation for, and benefits of `guard` statements just fine. If you are bizarrely confused or angered by them, it does not mean they are categorically bad.