[swift-evolution] Global init() functions

John McCall rjmccall at apple.com
Sun Nov 27 19:58:23 CST 2016


> On Nov 25, 2016, at 7:59 AM, Jay Abbott <jay at abbott.me.uk> wrote:
> Why not just say that this doesn't affect the removal of types (i.e. they can still be discarded) and add something to prevent specific types being discarded even if they're not statically used?
> 
> ```swift
> @nodiscard SomeType
> ```
> 
> This way, rather than a protocol opting in that anything implementing it is automatically `@nodiscard` a program or library would declare some types as non-discardable and could safely say "there will be at least 1 SomeProtocol" available" without saying what specific type it is in the public interface/docs?

In language design, we usually find that semantic annotations (which tell us what the programmer is trying to do) are superior to imperative annotations (which order the compiler to do something without much explanation why).  They allow the implementation to provide a better programming experience (e.g. giving useful diagnostics about likely errors and selecting more thoughtful default behaviors), they're easier to apply sensibly to new language constructs, and they allow better optimization with fewer unintended side-effects.  All of that applies here as well.

In this case, because it's really *the conformance of a type to a specific protocol* that should not be stripped, tying the annotation to the protocol is the more semantic approach, and as expected, it has a number of benefits.  Let's dig in to why.

First, if the annotation has to be on every conforming type, there's an easy error of omission that could lead to bugs.  The really unfortunate thing here is that it often *won't* lead to bugs, because you'll only miss it if the type is actually stripped, and there might be all sorts of reasons why a type that's "really" unused is not stripped — maybe the implementation isn't running the analysis at all (which it probably won't in unoptimized builds), or maybe the implementation of the analysis just isn't very good, or maybe you have test code that still uses the type, or maybe there's some old code still linked into the project.  We really don't want to encourage a situation where e.g. you have an old implementation of a library sitting around, and you really want to delete it but you can't because when you do it breaks the build (but only in release mode), and two years later someone actually has the time to look into it and figures out that it's because you were missing this annotation on a bunch of types.

In contrast, if the annotation is on the protocol, it's really easy to check at the point of iterating over a protocol's conformances that the protocol has the right attribute.  And in fact, we can use this iteration as the "root" use of all the conformances, so that e.g. if we can prove that nothing in the program actually iterates the conformances of that specific protocol, we can go back to dropping them.

Second, if the annotation is on types, it's unclear what exactly about the type can't be stripped.  Do we have to keep all of its code around just in case something unexpectedly links to it?  Do we have to keep around every protocol conformance it has?  What does that mean if we add the ability to synthesize protocol conformances, or imply conformances post-hoc from other conformances?

So that's why this belongs on the protocol, and I hope the discussion gives you an idea of how to approach similar problems in the future.

John.


More information about the swift-evolution mailing list