@main: Type-Based Program Execution

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.

30 Likes

It seems to me that the win here is very minor, because it only removes a single line of code. The purported benefit is to eliminate the main.swift file, but that file would only include a single static method call anyway, eg. Math.main().

On the other hand, the drawbacks are considerable. This proposal would make it more difficult and imposing to approach an unfamiliar application’s codebase. Whereas today one can start by looking at the main.swift file and explore outward from there, this proposal would allow programs to eliminate main.swift entirely.

In that scenario, one might conceivably need to search through every single file in the project just to locate the @main entrypoint. And even then, as shown in the Math example, the main function might be defined somewhere else entirely, such as a protocol extension in an imported module!

Now it’s true that main.swift could just as easily call into a type’s static function provided by an imported protocol extension, but at least in that case the function call is visible and right-clickable, and it is obvious what file the function call can be found in.

This proposal would impose a non-trivial cognitive load on developers joining or browsing an unfamiliar project, and I am not convinced that the meager benefit of removing a one-line main.swift file outweighs the cost of not knowing where to begin when looking at a project for the first time.

6 Likes

General thoughts:

  • It took me a bit to understand the ParsableCommand example. My first few readings of it were that @main would apply to the ParsableCommand protocol itself, and not one of the conforming types.
  • I like that this unifies @NSApplicationMain and @UIApplicationMain, even though that's a tiny corner case to need that.
  • I'm torn about whether the wins are really worth it: we're eliminating one line and one file. Yay?

Regarding the alternatives:

  • I agree this should probably be an attribute and not a protocol
  • I agree this should be a static method and not an instance method
  • I agree this should be called "main" and not something else. Something else would feel like change for the sake of change.
  • I think it's worth considering allowing different signatures for the main method, but I agree that that's easily added in the future
  • I agree with your rationale for returning Void instead of Never. Void is far more compose-able.

Questions:

  • What about declaring a function as @main?
  • What about retroactive @main-ness? To use your example, what if my library declares some public ParsableCommand types? They'll have a static main() by virtue of being ParsableCommand, but could the executable itself be a single file that does:
    @main extension MyCommand { }
    
    (As an example, I'm thinking of how the curl command is a negligible wrapper for libcurl)
2 Likes

Your concern seems hugely overblown, given that searching for “@main” is as easy as searching for a file “main.swift”. So your cognitive burden is basically zero.

2 Likes

a) Not every platform makes searching for text within files as easy as searching for filenames.

b) Okay, so I’ve found @main on the Math type, but it doesn’t have a static func main. In fact, there’s no static func main in the entire module. Now I have to search all the dependencies and figure out which protocol extension (or superclass) implementation of main is used by Math, just to understand where the program begins?

11 Likes

I've always disliked the idea of attributes that require method/subscript/property implementations, which is becoming increasingly popular with @dynamicCallable, @dynamicMemberAccess, @propertyWrapper, and others. If we want to require a method for a type, we should be using the tool originally designed for the job, protocols. You could define an EntryPoint protocol that requires a .main method:

struct MyProgram: EntryPoint {
    static func main(_ argument: [String])
}

Given, I'm sure there is valid reasoning behind the use of a attribute that acts like a protocol (I can't remember exactly what was stated in the pitches for those attributes), but it always leaves a weird flavor in my mouth, and the compiler diagnostics are lacking.

Anyway, just my 2¢.

6 Likes

Again, this seems hugely overblown given how easy content searching is. However, if protocols can provide an implementation, I do wonder how multiple conformance would work if a type conforms to multiple protocols providing implementations.

Sounds like god object.

I said the drawbacks are “considerable”, and the cognitive load is “non-trivial”. I stand by those descriptions, and I do not think they are overblown.

I am not claiming these are terrible and insurmountable problems, but I am suggesting that the benefit of eliminating a one-line main.swift file is itself quite small and does not clearly and obviously outweigh those costs.

Simply being able to open main.swift and right-click on whatever functions it calls, is a huge leg up in getting to know an unfamiliar program’s codebase.

I think you’re missing the point of @main that you don’t care how the execution begins.

Code is read far more often than it is written.

The person writing @main doesn’t care how the execution begins. They already know where to put the code they want to have run.

But all the many people reading that code forever and after, will each individually need to go through the same process of figuring out what it does without knowing where it begins. They don’t know which code is run first, and it is non-trivial for them to discover that.

6 Likes

I disagree with that. Code being maintained is read far more often than write.

Before @NSApplicationMain, there always are a small main.swift equivalent that calls the magic function. I read it once, and never care since then about what it does. I doubt anyone would care even if it is right-clickable. All I do is to make sure the callbacks in ApplicationDelegate are properly implemented.

It’s not that one should use @main for the sake of it. I think the pattern is common enough to warrant the feature. Even more so with the coming of ArgumentParser.

2 Likes

I just want to be clear that I am raising these concerns in order to strengthen the proposal.

I’m not trying to change anyone’s mind here, and indeed I don’t have a strong opinion either way on the idea being pitched. But I am quite convinced that the proposal text itself, as written, does not sufficiently motivate the changes it would bring.

The proposal needs to consider the drawbacks, and it should not trivialize the increased cognitive load. In order to make the case for this change, the proposal should demonstrate that the benefits it brings are compelling enough to be worthwhile despite the drawbacks.

So far I do not believe it has done so.

7 Likes

I recall somewhere that @dynamic* ends up being unneeded, and could be removed should one write a proposal (reducing the feature to magic function names).

I think @main needs to be attribute since multiple classes can conform to the protocol.

I think the proposal reduces cognitive load by defining a blessed pattern for "type-based program execution." Today, every library that needs to do this kind of thing probably arrives at a different solution. Tomorrow, @main will funnel the ecosystem towards a consistent solution.

4 Likes

As mentioned off-forum, I prefer longer more meaningful names that don't feel like they're mid-1970's. Other than that, I am really happy using this.

4 Likes

How is one supposed to test any aspect of the @main type?

I may be opinionated, but with the status quo, I think if any executable contains more than the following, it has been written wrong:

// main.swift
import MyProgram
MyProgram.main()

The reason is that when everything else lives in a library, all aspects of MyProgram besides main() can be be easily tested (and tracked by test coverage):

import MyProgram
import XCTest

class Tests: XCTestCase {
  func testMyProgram() {

    XCTAssertEqual(
      MyProgram(args: [], env: [:]).execute(),
      ExitCode.success
    )

    XCTAssertEqual(
      MyProgram(args: ["--help"], env: ["LANG": "en"]).execute(),
      ExitCode.success
    )
  }
}

But that doesn’t translate into a world with @main at all. The MyProgram struct cannot live in a library, because @main would turn the library into an executable. That also means anything that depends on or references MyProgram must also be expelled into the executable.

To be useful, @main would have to be applicable to extensions, not types. Then the executable would look like the following:

// MyProgram.swift
import MyProgram
@main extension MyProgram {}

But then I have to wonder whether it was worth it. All it did was allow me to rename main.swift to whatever I want, at the expense of adding 12 characters to the last line (−3: ., (), +15: @, _extension_, _{})


But I have nothing against the switch in syntax. If it were combined with features that dealt with the testing problem, I would be for it.

As a spitballed example, @main could be designed so that is is ignored when compiled with testability enabled. That way SwiftPM could have an executable target that was simultaneously importable as a library when used by tests.

8 Likes

It's funny, but I misunderstood how this proposal would work at first. Turns out it just took a slightly different route than what I expected. This is what I expected to see:

@main 
struct NSApplicationMain<AppDelegate: NSApplicationDelegate > {
    static func main() {
        // NSApplication startup code using the chosen delegate type
    }
}

And then you could apply this type as an attribute to your application delegate type:

@NSApplicationMain
class MyAppDelegate: NSApplicationDelegate {
   ...
}

And the generated main.swift would look like this:

NSApplicationMain<MyAppDelegate>.main()

So what I had in mind was a way to define custom attributes that could act exactly the same way as the built-in @UIApplicaitonMain and @NSApplicationMain we have today. We could even move the built-in ones out of the compiler and into their respective frameworks.


Using this proposal, it'd have to work something like this:

extension NSApplicationDelegate {
    static func main() { /* NSApplication startup code */ }
}
@main
class MyAppDelegate: NSApplicationDelegate {
   ...
}

I find that to be a strange pattern. It sorts of establishes a call hierarchy of this kind: Delegate.mainNSApplicationdelegate methods, where the application object is sandwiched in the delegate.

1 Like

Hi @nnnnnnnn,

Thanks for putting up this pitch. While I like the idea, I think that I would like this to be a bit more general for two use cases that I think should be considered in scope.

First, Windows has 4 different entry points with 2 being more important than the other two:

  • main ( (Int, UnsafeMutablePointer<UnsafeMutablePointer<CChar>>) -> Int)
  • wmain ((Int, UnsafeMutablePointer<UnsafeMutablePointer<WCHAR>>) -> Int)
  • WinMain ((HINSTANCE, HINSTANCE, UnsafeMutablePointer<CChar>, CInt) -> CInt)
  • wWinMain ((HINSTANCE, HINSTANCE, UnsafeMutablePointer<WCHAR>, CInt) -> CInt)

The interesting ones are of course wmain and wWinMain as they are Unicode and give you more than the ASCII encoded arguments for command line and UI applications respectively. This definitely doesn't work for the signature that you specify - there is a bunch of grungy work that needs to be done to parse the command line if we want to always do the argc, argv signature.

The second case is something that I've idly wanted for a while - having a framework specific entry point which can be marked on a struct/class which would be instantiated and then have a method called on it. Ideally this would be a struct/class wrapper (similar to property wrappers). This would allow the framework to initialize however it wants and hand off to the instance that is annotated with the attribute. In fact, pushing this into the wrapper gives the framework full control over the method name and implementation.

6 Likes

I understand your concern here, but I think searching for @main isn’t an insurmountable burden. Hopefully project maintainers will also make it obvious through documentation and/or project organization where the starting point is.

All in all, remember that this isn’t really a new feature — it’s a generalization of the behavior afforded to UIKit and AppKit. For those frameworks, we have two different attributes whose sole job is to eliminate the requirement to add a main.swift file with some startup boilerplate.

I’m not too concerned about this. Since the intent is that frameworks will provide the actual entry points, that will be one of the first things you’d have to learn anyway when working with such a framework.

Thanks for this point — the question of applying @main to extensions has been brought up before (and here in this thread by @davedelong), but I didn’t have a great example for why that would be needed. Separating the root type out into its own module is a useful perspective.

2 Likes