I'm in favor of this, and I think it's helpful to break it into two parts to see why it is the way it is:
- A common signature for entry points.
- A way to use an entry point for a program without top-level code.
I'll start by talking about #2 first, and I want to make it clear that this is definitely justified. Swift got @NSApplicationMain
and @UIApplicationMain
in Swift 1.1, so there was interest at the time in getting rid of the vestigial main.swift. There's not much point in knowing that a program starts in main.swift just to see that it immediately enters a library function for the rest of the program, especially if that library function is some kind of event loop; it would be more sensible to have that logic attached to a type that's already relevant.
What I wouldn't want to see is people using this when there's no relevant type (for example, making a dummy type for calling dispatchMain()
). But I guess I can't stop people from doing things with a feature that I wouldn't.
#1 is the other interesting part because it kind of gets glossed over. It's certainly possible to write a protocol for this:
protocol ProgramMain /* or EntryPoint, or whatever */ {
static func main()
}
But then what? This just declares that a type can be used as an entry point; it's still up to an application which type to use as an entry point. The attribute (or some other annotation) is still necessary to make this feature work.
That said, it's a reasonable design for @main
to only work on types that conform to ProgramMain. What would the implications of that be? The only one I can think of is that conforming to NSApplicationDelegate wouldn't be enough; AppKit apps would have to make use of both protocols, and likewise for UIApplicationDelegate / UIKit apps.
// Before
@NSApplicationMain
class AppDelegate : NSApplicationDelegate { … }
// After
@main
class AppDelegate : NSApplicationDelegate, ProgramMain { … }
Honestly, that doesn't seem like too much of a burden to me, and so I'd prefer a formal protocol rather than another member name the compiler magically recognizes. (Of course the protocol is still a magic name the compiler references, but it makes it easier to document and to point out what someone's forgotten to do.)
On the notes about types, I agree with @Chris_Lattner3 that we ought to accept throwing main
functions, inserting a top-level try
before the implicit call if necessary. That costs us nothing. What'd be a little trickier is accepting Never-returning main functions. I think the current design with Void-returning main
is a good one, not in the least because it's the simplest thing for someone to write when they haven't learned about Never yet, but it would be nice™ if Never were accepted. Unfortunately, doing that with a protocol is clunky:
protocol ProgramMain {
static func main() throws
static func main() throws -> Never
}
extension ProgramMain {
public static func main() throws {
try main() as Never
}
public static func main() throws -> Never {
try main() as Void
exit(0) // or equivalent
}
}
Without extra compiler support, this means someone could conform to ProgramMain without implementing either method and run into an infinite loop. Oops.
As for the name, I do think @main
is a little ambiguous on a type (does being the "main struct" mean it's the most important?), but I tend to agree that the precedent in other languages makes it worth it for both the attribute and the entry point itself.
EDIT: I missed this on my first read
The @main
-designated type can be declared in the application target or in an imported module.
and definitely think putting @main
in a library should not be allowed. While @main
on a type in your own code does require you to check the type's inheritance to see who's responsible for starting the program, pulling in @main
via imports means a reader of the code would have to check all imports, transitively, for whether they have an entry point type. And if two imports have an @main
, which one wins?