I ended up writing some code that had a lot of possible exit conditions. There's one line of code that always needs to run when it exits, so I put it in a defer block near the top of the method.
There’s a line of code that needs to run at the end of the method (after everything else has succeeded), but must execute after the deferred statement earlier. So I put it inside a defer block at the very end of the method, and got a compiler warning:
'defer' statement before end of scope always executes immediately; replace with 'do' statement to silence this warning
I had expected that all deferred blocks (seen so far) would execute in sequence when scope exited. Why is a defer block at the end of scope treated differently? This seems like an inconsistency from the more desirable behavior I expected.
It's actually useful when you want to pair allocation-deallocation code
func foo() {
let data1 = ...
defer { deallocate(data1) }
let data2 = ...
defer { deallocate(data2) }
// Now deallocate 2
// Now deallocate 1
}
Which can be especially tricky if data2 depends on data1.
Plus I usually put it as a reminds me to do this kinda code.
So I put it at the earliest line I realize I need to do something (usually cleanup), which I think is in the spirit of defer.
It makes a lot of sense when the defer statements build on one another:
func queryWithSocket(options: Options) -> Data {
let socket = try Socket.requestFromSystem(options: Options)
defer { socket.releaseToSystem() } // Needs to run last.
try socket.setUp()
defer { socket.tearDown() } // Needs to run 2nd last.
try socket.open()
defer { socket.close() ) // Needs to run 3rd last.
return try socket.waitForResponse()
}
The order is integral to defer and part of what makes it so useful. It is designed particularly for paired constructors and destructors, where deconstruction order is—logically—opposite to construction order. It does not matter which try statement above fails, defer allows the abort process to safely back out in the right order and leave no mess behind.
I guess I can see that. But not so useful when you want to do this:
session.beginConfiguration()
defer { session.commitConfiguration() } // must always be done
// some session configuration
if error { return }
// more session configuration
if error { return }
…
session.start() // Requires configuration be committed, so a defer { session.start() } would be nice, if it went in order
You want to configure something, and commit regardless of what happens. It'd make sense that it forms a boundary that say Ok, configuration is done/has failed, what now?. It does add an indentation as you said. I personally think it'd be better to form its own function.
I'd like to echo what you said about making configuration a separate function. In this example, configuration is logically separate from starting. In fact, I'd probably write the calling code something like this...
let session = try configureSession()
session.start()