Do we have non-escaping local variable type?

In this recent thread: An odd error: "Escaping closure captures mutating 'self'" - #10 by Jens, I, (well, actually @Jens), just found out that this code compiles:

func test(_ callback: () -> Void) { // Compiles, no need for it to be @escaping
    let x = callback
    x()
}

It baffles me because I don't think we have non-escaping closure types (yet). So my questions are

  • Do we have it, and
  • If so, how do we spell the type of x (or if it's actually a declaration attribute)?

I confirmed that this code compiles in Xcode 12.5.1 but fails to compile in Swift Playground 3.4.1.

1 Like

Any closure that is not explicitly marked as @escaping is non-escaping. Why would the @escaping attribute exist if non-escaping closures didn't exist? What would be the point?

The reason why the above code compiles is because the closure does not escape the test function.

That only applies to function/method/closure parameters. All struct/class members are (necessarily) escaping by default, and so are the enum's associated values. For local variables, non-contexted closures are escaping by default.

What I do not know is the existence of non-escaping local closure variables. It is theoretically possible, but I don't recall any formal proposal (and feel like it might be needed, given the code below could cause confusion), hence the question.

We have non-escaping closure parameters, which is the default. But, unfortunately, that is irrelevant since my question is about local variables.

That is not exactly an ideal explanation, given the compiler fails to realize that y below never escapes the test function:

func x(y: () -> ()) {
  // Error: Passing non-escaping parameter 'y' to
  // function expecting an @escaping closure
  let z = y ?? {}
}
1 Like

I suppose your code will be interpreted in this way, that's why you need to use @escaping, check this Swift optional escaping closure - Stack Overflow for more details.

func x(y: () -> ()) {
  // Error: Passing non-escaping parameter 'y' to
  // function expecting an @escaping closure
    let z = Optional.some(y) ?? {}
}

Thanks for the link. It is indeed the case that my code may be using an optional promotion or casting to a generic type, either of which may escape. However, it is just a contrived example of why "intuitively" looking at the code behaviour isn't the perfect way to figure out what the compiler is doing.

My question remains:

  • Does non-escaping variable exist, and
  • How do I type-annotate it?
1 Like

This doesn't fully answer your question, but as an interesting curiosity:

public func areEqual(callbackA: @escaping () -> Void, _ callbackB: () -> Void) -> Bool { 
    return type(of: callbackA) == withoutActuallyEscaping(callbackB) { type(of: $0) }
} 

when compiled with -Ounchecked compiles down to return true (and you can also run it to see the same result), meaning that at some level the compiler considers the escaping and non-escaping types to be equivalent (caveats about withoutActuallyEscaping potentially causing weird behaviour aside). That might not be telling us much, though – it could just be that e.g. @escaping doesn't get encoded into the mangled type name.

2 Likes