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
- Proposal: SE-NNNN
- Authors: Nate Cook, Nate Chandler, Matt Ricketson
- Review Manager: TBD
- Status: Pitch
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.