Hi all —
This is a pitch for adding a new attribute that you can use to designate a type that provides the entry point for a Swift program. This is a generalization of the @UIApplicationMain and @NSApplicationMain attributes that have been in Swift from the beginning, making that specialized behavior available to any library or framework.
Please let me know what you think!
Nate
@main: Type-Based Program Execution
Introduction
A Swift language feature for beginning program execution with a designated type instead of with top-level code.
Motivation
Swift programs start execution at the beginning of a file. This works great for procedural code, and allows simple Swift programs to be as short as a single line, with no special syntax required.
// A self-contained Swift program:
print("Hello, world!")
However, not all kinds of programs naturally fit into this model. For example, a user-facing app launches and runs until it is quit. User interface frameworks like UIKit and AppKit take care of the complexities of launching an application, providing high-level API hooks for defining app behavior. A developer using these frameworks typically does not care about nor interact with the literal starting point of their app’s execution.
In order to resolve these two models, apps need to provide a small amount of “boot-loading” code to kick off the framework’s preferred execution entry point. Ever since its initial release, Swift has provided the domain-specific attributes @UIApplicationMain and @NSApplicationMain to smooth over this startup process for developers of UIKit and AppKit applications.
Instead of these hard-coded, framework-specific attributes, it would be ideal for Swift to offer a more general purpose and lightweight mechanism for delegating a program’s entry point to a designated type. A type-based approach to program execution fits within Swift's general pattern of solving problems through the type system and allows frameworks to use standard language features to provide clean, simple entry point APIs.
Proposed solution
The Swift compiler will recognize a type annotated with the @main attribute as providing the entry point for a program. Types marked with @main have a single implicit requirement: declaring a static main() method.
When the program starts, the static main() method is called on the type marked with @main. For example, this code:
@main
struct MyProgram {
static func main() {
// ...
}
}
is equivalent to:
struct MyProgram {
static func main() {
// ...
}
}
// In 'main.swift':
MyProgram.main()
Since main() is a regular static method, it can be supplied by a protocol as a default implementation or extension method. This allows frameworks to easily define custom entry point APIs without additional language features.
For example, the ArgumentParser library offers a ParsableCommand protocol that provides a default implementation for main():
public protocol ParsableCommand {
// Other requirements
}
extension ParsableCommand {
static func main() {
// Parses the command-line arguments and then
// creates and runs the selected command.
}
}
The @main attribute would allow clients to focus on just the requirements of their command-line tool, rather than how to launch its execution:
@main
struct Math: ParsableCommand {
@Argument(help: "A group of integers to operate on.")
var values: [Int]
func run() throws {
let result = values.reduce(0, +)
print(result)
}
}
Likewise, UIKit and AppKit could add the static main() method to the UIApplicationDelegate and NSApplicationDelegate protocols, allowing authors to use the single @main attribute no matter the user interface framework, and allowing for the deprecation of the special-purpose attributes. (Note: These changes are not a part of this proposal.)
Detailed design
The compiler will ensure that the author of a program only specifies one entry point: either a single, non-generic type designated with the @main attribute or a single main.swift file. The type designated as the entry point with @main can be defined in any of a module's source files. @UIApplicationMain and @NSApplicationMain will be counted the same way as the @main attribute when guaranteeing the uniqueness of a program's entry point.
A main.swift file is always considered to be an entry point, even if it has no top-level code. Because of this, placing the @main-designated type in a main.swift file is an error.
@main can be applied to the base type of a class hierarchy, but is not inherited — only the specific annotated type is treated as the entry point. @main cannot be applied to a type defined in a framework or library.
The rules for satisfying the static func main() requirement are the same as for satisfying a protocol with the same requirement. The method can be provided by the type itself, inherited from a superclass, or declared in an extension to a protocol the type conforms to.
Other considerations
Source compatibility
This is a purely additive change and has no source compatibility impacts.
Effect on ABI stability and API resilience
The new attribute is only applicable to application code, so there is no effect on ABI stability or API resilience.
Effect on SwiftPM packages
The @main attribute will currently not be usable by Swift packages, since SwiftPM recognizes executable targets by looking for a main.swift file.
Alternatives
Use a special protocol instead of an attribute
The standard library includes several protocols that the compiler imbues with special functionality, such as expressing instances of a type using literals and enabling for-in iteration for sequences. It would similarly be possible to define a protocol Main instead of creating a @main attribute, with the same requirements and special treatment. However, such a protocol wouldn’t enable any useful generalizations or algorithms, and the uniqueness requirement is totally non-standard for protocol conformance. These factors, plus the precedent of @UIApplicationMain and @NSApplicationMain, make the attribute a more appropriate way to designate a type as an entry point.
Use an instance instead of static method
Instead of requiring a static main() method, the compiler could instead require main() as an instance method and a default initializer. This, however, would increase the implicit requirements of the @main attribute and split the entry point into two separate calls.
In addition, a default initializer may not make sense for every type. For example, a web framework could offer a main() method that loads a configuration file and instantiates the type designated with @main using data from that configuration.
Use a different name for main()
Some types may already define a static main() method with a different purpose than being a program’s entry point. A different, more specific name could avoid some of these potential collisions.
However, adding @main isn’t source- or ABI-breaking for those types, as authors would already need to update their code with the new attribute and recompile to see any changed behavior. In addition, the main name matches the existing behavior of main.swift, as well as the name of the entry point in several other languages—C, Java, Rust, Kotlin, etc. all use functions or static methods named main as entry points.
Use (Int, [String]) -> Int or a similar signature
C programs can define a function with the signature int main(int argc, char *argv[]) as their entry point, with access to any arguments and returning a code indicating the status of the program. Swift programs have access to arguments through the CommandLine type, and can use exit to provide a status code, so the more complicated signature isn't strictly necessary.
To eliminate any overhead in accessing arguments via CommandLine and to provide a way to handle platform-specific entry points, a future proposal could expand the ways that types can satisfy the @main requirement. For example, a type could supply either main(), main(Int, [String]) -> Int, or another platform-specific entry point. Adding the @main attribute provides a location for this kind of customization that Swift doesn't have with the current file-based approach.
Return Never instead of Void
A previous design of this feature required the static main() method to return Never instead of Void, since that more precisely matches the semantics of how the method is used when invoked by the compiler. That said, because you can’t provide any top-level code when specifying the @main attribute, the Void-returning version of the method effectively acts like a () -> Never function.
In addition, returning Void allows for more flexibility when the main() method is called manually. For example, an author might want to leave out the @main attribute and use top-level code to perform configuration, diagnostics, or other behavior before or after calling the static main() method.