Stack explosion or tail recursion?

With this function:
func startReceive()
{
self.connection.receive(minimumIncompleteLength: 1, maximumLength: 1)
{ data, contentContext, isDone, error in

		//	do something with data; handle errors and EOF
		
		self.startReceive()  //	stack explosion or tail recursion?
    }  

}

what will happen, if 1 MB is sent?

A: the stack will explode
B: Swift does handle tail recursion and the stack size will not change.

Gerriet.

Could be either, or neither, depending on what exactly self.connection.receive does with the closure you pass to it.

For something like this usually the closure is saved and is called at later time. And thus, despite the appearances, you might not be calling startReceive within itself here.

Swift does support tail call optimizations, but it doesn't make any guarantees. In effect, you could never rely on it, unless you're carefully retesting your code with every compiler version.

The main issue is that functions have a "prelude", a chunk of code that runs after the function body, which makes release calls on any ARC-managed objects used during the function.

So long as your function's last statement is a recursive call (with no extra bells and whistles, like recurse() + [value], recurse() + 1), and your functions doesn't use any ARC--managed objects, then the compiler should be able to tail-call optimize it. But again, don't rely on it.

In any case, the example you show is most certainly not a good dit for recursion. Conceptually it's just messy, but it's also not a candidate for tail call optimization because data and probably contentContext are arc-managed.

Also see https://www.natashatherobot.com/functional-swift-tail-recursion/

3 Likes

Given the context here, the receive(…) method is clearly from NWConnection, you will never recursively call yourself. NWConnection will not call the supplied closure synchronously. Rather, it always dispatches that work to the queue associated with the connection.

Share and Enjoy

Quinn “The Eskimo!” @ DTS @ Apple

1 Like

It's been my experience in Swift that you should just assume tail-call optimization won't happen. Since trying to actually write code that will trigger that optimization requires deep knowledge of the optimizer and how Swift codegen is done. It also probably requires some deep knowledge of the LLVM optimization passes as well.

2 Likes

eskimo Quinn “The Eskimo!”
June 12
Given the context here, the receive(…) method is clearly from NWConnection, you will never recursively call yourself.

Correct. It is even your code (from some Apple Forum); to be fair though: your code uses a more reasonable value for maximumLength.

NWConnection will not call the supplied closure synchronously. Rather, it always dispatches that work to the queue associated with the connection.

Would be nice if the documentation would mention this.

Regarding the more general question of tail calls: there once was a discussion about "Proposal: Tail Call Optimization keyword/attribute”.
What happened to this idea?

Gerriet.

Would be nice if the documentation would mention this.

Please do file a bug against the docs about such things.

Regarding the more general question of tail calls

I can’t help you on that front, alas. I try to stay focused on The Now™ rather than The Future™.

Share and Enjoy

Quinn “The Eskimo!” @ DTS @ Apple

1 Like