This doesn't mean that they don't exist and it doesn't mean that the documentation isn't wrong...
No, but it means that I donāt agree on the premise that it is a problem worth āfixingā with new syntax.
I respect your point of view, but I disagree with it
In the vein of @cukrās solution up-thread, I feel like this falls into the bucket of āproblems that have elegant solutions in a world with variadic genericsā:
func noOp<In...>(args: In...) {}
func noOp<In..., Out>(args: In...) -> Out? { return nil }
// ...
func doSomething(completion: (Bool) -> Void = noOp) {
// ...
}
Iām gonna have to agree with @sveinhal here that while this does seem an error in language design, each change to the language has to be weighed for its objective cost and benefits.
In this case, the cost is relatively high - new keywords to avoid ABI incompatibility. The benefit is relatively low: you can workaround this with a no-op closure, or simply documenting that the closure will in fact not escape. @escaping
itself is not a guarantee of escaping behaviour, anyway, but simply an indicator that the closure may escape. And unless youāre doing a ton of work in this closure, the escaping nature should have a relatively small impact, if any at all.
Iām not sure we should be designing language features to work around relatively small defects.
The goal of the Swift project is to create the best available language for uses ranging from systems programming, to mobile and desktop apps, scaling up to cloud services. Most importantly, Swift is designed to make writing and maintaining correct programs easier for the developer.
The most obvious way to write code should also behave in a safe manner. Undefined behavior is the enemy of safety, and developer mistakes should be caught before software is in production. Opting for safety sometimes means Swift will feel strict, but we believe that clarity saves time in the long run.
Swift is not an e-commerce app where you can say "not worth the time", guys.
Objectively this is a design flaw that allows undefined behaviours. You can't just close one eye and dismiss the matter by saying "High cost, low gain". It's not a matter of gain, it's a matter of making things right.
What is the undefined behaviour?
This entire thread has gone seriously off the rails.
We are not going to invent a whole new language mechanism for expressing parameter function optionality. It should be quite straightforward to extend escape analysis to optional parameter functions and allow them to be explicitly marked as @escaping
or @nonEscaping
(or however we want to spell that). We can also investigate changing the default language rule for optional parameter functions so that it's consistent for different optionalities, but that's a separable question.
I don't think the "right" solution of making the default non-escaping for optional function parameters is that onerous.
The compiler will tell you if you need to add @escaping
somewhere, and the migrator will fix this for you automatically. The migrator could be conservative and always add @escaping
in public function signatures to avoid changing the contract, keep the same mangling, and preserve ABI compatibility.
A migration path for making parameters of public functions non-escaping while keeping ABI compatibility with previous versions of a library would then be to add an attribute for using the old mangling for that function. Something like @manglingCompatibility(Swift 5)
or similar.
If it has chances to be approved, I'd be happier to have optionals propagating escapability. That was my first pitch but I was told it's impossible, then I came up with this other idea.
I'll put together an official proposal then.
Sure, undefined behaviour should be fixed.
However, I fail to see that anything around this is currently undefined.
Seeing an optional closure in a method signature, without knowing the actual implementation of this method, leaves undefined if the closure will actually return before or after the return of the method. The compiler assumes that the closure is escaping but just as side effect of how Optionals are defined, not because of the actual escapability behaviour of the closure within the function. Like I said in my first post of this pitch, the documentation states that a closure is escaping the function if it is invoked after the function returns. with optional this is not necessarily true.
Imho the issues with Optional
are quite deplorable, because it is such a fundamental concept of Swift - but I don't think the capture behavior something that causes real pain for "regular" users of the language.
Couldn't this specific shortcoming be fixed later on with the additions needed for the memory ownership story? This is a topic which has been looming for quite a long time, and still has no complete design (at least I've never seen one) - but afaics, borrowing is a closely related concept.
That's not what "undefined behaviour" means in this context. "Undefined behaviour" is a term of art in the context of programming languages, and means that even given the implementation code, a user can still not reason about how the program will function, and that the program may function in unpredictable (to the compiler/computer) at run time.
The fact that the method signature does not convey enough information to infer what it does, is a sign or poorly named and/or documented code. This can happen in a number of ways, even with this new keyword.
Why should optional closures be special? From a point of view of the user of your apis it is an undefined behaviour, and you must assume no-matter-what that it will escape.
Leave aside poor choice of documentation and keywords, because with this reason you could even argue that we don't need @escaping at all. This ambiguity that optional creates with closures should in my opinion be fixed.
Drop the new keyword, please. The conversation evolved and we agreed on the fact that we can do it having optionals propagating escapability (which is what I proposed in the first pitch on this matter)
Yes, we could get away without having @escaping
at all. We could assume that a closure could always escape, and Swift would still be well-defined.
However, having @escsping
is nice, both for the compiler and for users. Likewise, having it for optional closures would also be nice.
Iām not arguing against its utility.
Iām arguing that not having it, does not constitute undefined behavior. Secondary, Iām arguing that introducing new keywords, is not worth the cost. And thirdly, Iām arguing that non-escaping optional closures are pretty niche.
But it is obviously a missing feature that you are not able to declare such closures, and it means that (the well-defined) behavior must be communicated through some other means, such as naming and/or documentation.
So everyone agree. We can go back to the initial pitch, but not blindly: @John_McCall has provided a guidance that should not be forgotten:
Unless I can't read, this gives two pitches:
- The ability to mark optional closures as
@escaping
or@nonEscaping
- A change in the default rule for optional parameter functions (making them
@nonEscaping
by default, just as non-optional parameter functions).
Each pitch would come with its own focused rationale. If possible, let's perform an exploration about the detailed design, and impacts on API and ABI stability. Let's not pretend those questions do not exist if we can't answer them: instead, let's ask knowledgable people.
For example, is there any impact on withoutActuallyEscaping(:do:)
? Even if there is no impact at all, stating it clearly avoids leaving "undefined behaviors" ;-) in the pitches.
Should we continue the conversation here or on Allowing @escaping for optional closures in method signature
Since we have been advised to explore two pitches, I'd say that focused threads are recommended. Should we discuss them in parallel? I have no opinion on the subject. What is sure is that pitch 2 depends on pitch 1, and pitch 2 is one of the motivations for pitch 1 . We'll have to be able to motivate pitch 1 without pitch 2!
They aren't. You are using terms differently than they are typically used in programming, and this is causing confusion.
Undefined behavior refers to a situation in the which the compiler makes no effort to guarantee a predictable result. In C, integer overflow is considered undefined because the compiler makes no assumption about what the CPU will do. In Swift, it is not. Overflow will either trap or be truncated, depending on the operator chosen; it's behavior is well-defined.
If the compiler assumes an optional closure will escape, it can, and does, generate code that works in a predictable way, even if the closure does not in fact escape. The behavior is not undefined, because it is predictable.
Your use of the term would preclude compiler optimizations, unless the developer added hints in the code to request them. I don't think this is what you are trying to argue.