I'm considering no longer covering the fallthrough statement in my programming courses. It's a statement I never use in my own code and I struggle to find examples that aren't artificial or toy examples.
Do any of you have real-life examples where fallthrough is either required, or results in cleaner code compared to other control flow?
I mean, personally, I would just mention it in passing when discussing switch. But I'm no teacher, so I don't know if just mentioning it would entice confusion/poor-use. Generally I rarely use it, and when I am, it's usually in some code that is trying to be a little too cheeky for its own good.
So I would say it's probably safe to leave it out.
Off-topic: I'm also curious, do you cover loop labels in your course? I remember in my introduction to programming course, the professor explicitly didn't cover them (this wasn't Swift, but a few languages support them), and would actually dock points if you used them in your assignments. This annoyed me to no end since they are pretty handy for some problems.
I would be hard-pressed to argue that the standard library is an example of "good swift", since we're willing to jump through many hoops in the name of performance for everyone else, but here's a fall through from the implementation of String. The context here is that other uses of this enum want to handle tagged pointer NSStrings separately, but this one has no need for special handling:
switch knownOther {
case .storage:
return _nativeIsEqual(
_unsafeUncheckedDowncast(other, to: __StringStorage.self))
case .shared:
return _nativeIsEqual(
_unsafeUncheckedDowncast(other, to: __SharedStringStorage.self))
#if !(arch(i386) || arch(arm))
case .tagged:
fallthrough
#endif
case .cocoa:
/* implementation for NSStrings here */
Not that I can share. However, it has been invaluable when it is needed. It should at least be mentioned.
Also, you might want to talk a few minutes about it in case your students need to be familiar with other languages where fall through is the default. There is a lot of C, etc., code out there which relies on that convention.
One reason to mention it would be to make it clear the default behavior is different from other languages. If your students ever used switch in one of the many languages where "falling through" is the default, showing them a fallthrough keyword is surely the easiest way to make them realize Swift's behavior is different.
IMO it should generally be avoided, and is generally not very "Swifty". I had forgotten it was even there when it was brought up recently
I think it should be seen as an advanced feature only to be used in very specific performance and/or legacy-code-compatability reasons. It depends on the audience whether it should be tought, but FWIW I expect that are many people currently getting paid to write Swift that don't even know about it .
It's breaks the normal control flow and kind of (IMO) the "Switch" metaphor, makes it so you have to read it very carefully to know the expected behavior, and behaves like nothing else does in Swift.
It feels like the kind of inherently unsafe (in terms of easily produced bugs and unexpected behavior when refactoring) thing that Swift has generally done away with.
I have a non-toy example. It seems to be very natural to use while splitting a cubic Bézier curve into two curves at a given point using de Casteljau's algorithm. Or at least it does to me. I’m splitting curves because I have a 20” by 12” laser cutter and want build objects larger the. 20” by 12” and sometimes this is easier to do by just making the larger object splitting the curves, and adding some notches and whatnot down one edge.
As a practical example look at the wooden shelving in the corner of this desk:
You're probably not looking for feedback, so feel free to ignore this as it's not intended as criticism. However more generally I think this is a good example where fallthrough isn't necessary, but makes legacy code easier to convert to Swift.
For instance instead of the for loop:
You could instead do:
left.append(points[0]) //points always has at least 2 elements, so this is safe
left.append(points[newCount]) //equal to newcount -1 + 1
(0 ..< newCount).forEach {
newPoints.append(points[$0] * (1 - t) + points[$0 + 1] * t)
}
I'm not familiar enough with your problem space to quickly grep what the full function does, but I believe the logic is identical. It should even be faster since the switch wouldn't be evaluated every iteration.
Given it looks like this was an existing algorithm that was converted to Swift, I would probably just use fallthrough as well but there are very few situations where there aren't other approaches that achieve the same result.
It absolutely was converted from something else...I think pseudocode. There will always be another way to do it, Swift is touring complete after all. The question is "is the other way always better (or at least as good)".
I'm going to have to withdraw this example from "argument for (teaching) fallthrough", as your rewrite is a definite improvement.
In my defense the runtime of this Swift program is measured in seconds (and the runtime if you ignore the box packing to fit the design on an approximation of a minimal number of sheets of wood is really more like measured in "second"), but the actual cutting time is measured in tens of minutes I was optimizing for minimal bugs in minimal time to write. Not so much runtime.
Maybe I'll have to come up with a reason to implement some sort of p-code interpreter in Swift, I remember taking advantage of C's fall through the last time I did one of those...
This post got me thinking. Fallthrough seemed like the most obvious thing to me when I was first learning Swift, because of the expectation I built from C. However, almost all of the use-cases I would have had for fall through, are better handled by comma delimited cases.
It could have not existed all along, and I wouldn't have noticed, if not for the misleading expectation I picked up from C.