4

In the following example, where test closure is passed in as a function parameter it does not requires @escaping. Does it mean it counts as a noescape closure? I'm wondering if this is a work around to avoid heap allocation caused by escaping.

func test() {
    print("hello")
}

class b<T> {
    let closure: T
    // does not requires init(c: @escaping () -> Void)
    init(c: T) {
        self.closure = c
    }
}

var c = b(c: test)
Hamish
  • 78,605
  • 19
  • 187
  • 280
jack sexton
  • 1,227
  • 1
  • 9
  • 28
  • 2
    closure doesn't necessarily means asynchronous – Leo Dabus Jan 19 '17 at 03:04
  • @LeoDabus what do you mean by that? – jack sexton Jan 19 '17 at 03:05
  • I don't think that generics make any diffrents, to the definition of escaping and non escaping closures. In your example as far as i can see there is no escaping closure. – Dasem Jan 19 '17 at 03:25
  • + class initializers in swift don't return a value, so there is no way for the closure to escape. – Dasem Jan 19 '17 at 03:32
  • @Dasem oh that makes sense – jack sexton Jan 19 '17 at 03:33
  • @Dasem but if I change the class to be non generic it requires the escaping attribute on the init, doesn't that contradict what you said or am I misunderstanding? – jack sexton Jan 19 '17 at 03:35
  • Interesting question. Please post the non-generic version showing the error too. – shallowThought Jan 19 '17 at 08:05
  • `class initializers in swift don't return a value, so there is no way for the closure to escape` This sentence makes absolutely no sense. A closure escapes if it outlives the lifetime of the function that it is passed to. The return value of the function has nothing to do with this. – Peter Schorn Sep 29 '20 at 17:50

2 Answers2

2

Does it mean it counts as a noescape closure?

No, certainly not. It escapes the lifetime of the function that it's passed to (init(c:) in this case) – thus by definition, it is escaping. You just don't have to mark it as @escaping because only closure function arguments (i.e function arguments that are typed as functions themselves) are non-escaping by default (as per SE-0103).

Therefore, because the closure is escaping, it will need any state that it captures to be heap-allocated. There's no possibility that this state can be stack-allocated, as its lifetime is not limited to the current stack frame.

However in your case, you're just passing in a simple global function, test. No additional heap allocation need take place here as global functions are stored statically, thus only a simple pointer needs to be passed about.

Although it's worth noting that because that you're working with generics, in order to type a function as a given generic placeholder T, Swift will use a reabstraction thunk in order to unify the calling convention. To do this, a heap-allocated box will be used in order to store the function value (I go into this in more detail in this Q&A).

However, in an optimised build, it appears the thunk is able to be specialised in some cases (such as with direct usage of a top-level function), therefore meaning no additional heap-allocation need occur.

Community
  • 1
  • 1
Hamish
  • 78,605
  • 19
  • 187
  • 280
1

Does it mean it counts as a noescape closure?

Your generic version tricks the compiler.

The compiler seems to look for any method arguments that are of type closure and are used within the method. Without checking how it is used, e.g. if it is actually called:

class Test {
    var closure: Any
    init(c: ()->Void) {
        self.closure = c //Error: Non-Ecaping parameter 'c' may only be called
    }
}

By setting the closure to a generic type (T) it ignores the fact that T might be a closure. If it would not ignore this, it had do complain on every generic argument, as it might be a closure.

I'm wondering if this is a work around to avoid heap allocation caused by escaping.

Opinion based: Compiler warnings and errors are there for a reason. I plead against suppressing them, especially for premature optimization reasons like "work around to avoid heap allocation".

shallowThought
  • 19,212
  • 9
  • 65
  • 112