3 ms·
Guard statements are great. First of all, this is still perfectly valid swift: if self == nil { return } But guard is really nice, because it signals to
by skohan 6y ago
Guard statements are great.
First of all, this is still perfectly valid swift:
if self == nil { return }
But guard is really nice, because it signals to the reader of a code block that if the guard condition can't be met, execution cannot continue past the guard. It does a lot to signal intent which using an if statement for early return does not.
It's something I sorely miss in Rust for example.
- dmitriid 6y ago> It does a lot to signal intent which using an if statement for early return does not. Since every single language has an if statement, we clearly know what intent an if block signals. If the if condition is not meant, execution cannot continue. See? Easy. The only reason guard exists is not to aid the reader, but to aid the compiler to recognise nullability checks. That's about it. It really is a very specific if statement that is given a special treatment. And all comparisons of how great it is compared to an if statement I've seen are hillariously funny in how they willingly lie about ifs. Starting with the actual Swift docs [1]: > 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 Here's how people take it to heart [2]: > Without using guard, we’d end up with a big pile of code that resembles a pyramid of doom. This doesn’t scale well Nope, you don't end up with a pyramid of doom, and yes it does scale well, provided you language designers actually design the language. [1] https://docs.swift.org/swift-book/LanguageGuide/ControlFlow.html https://docs.swift.org/swift-book/LanguageGuide/ControlFlow.... [2] https://thatthinginswift.com/guard-statement-swift/ https://thatthinginswift.com/guard-statement-swift/
- 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.