SE-0281: @main: Type-Based Program Entry Points

In general, since the intent of this feature is to allow frameworks to be able to provide standard entry point logic, I would resist the temptation to gold-plate this feature with too many different convenience options and possibilities, to keep the complexity down. In particular, I don't think it's unreasonable to expect that if a framework can provide top-level entry point logic, it can also provide error handling, process teardown, etc. all within a plain main() -> Void function.

8 Likes

In general I'm +1 on this feature, but I'm a little disappointed that it is not usable for Swift Packages.

Is this just a scope limitation of the proposal, or is there significant work to be done to bring support for it to SwiftPM that warrants a separate proposal? Or is it a "just when someone finds the time to add support for it"?

I'd really like to say that this should be an "underscored" feature, until the entire Swift ecosystem can take advantage of it.

2 Likes

I'm happy with the proposal and I think it'll simplify building all kinds of apps/systems, also on the server or command line :slight_smile:

  • What is your evaluation of the proposal?
    • :+1: I like it, it'll allow building frameworks which take advantage of this to simplify common "set up these 20 options and you're good" into a simple "extend this thing which preconfigures stuff for this common case".
  • Is the problem being addressed significant enough to warrant a change to Swift?
    • I think it's a worthy addition, seems there exist specialized entry points for mac/iOS apps and this generalizes it nicely.
  • Does this proposal fit well with the feel and direction of Swift?
    • Annotation for this rather than "magic protocol" sounds good for this, as was discussed in prior threads.
  • If you have used other languages or libraries with a similar feature, how do you feel that this proposal compares to those?
    • in Scala this is solved by a "magic trait" called App, though it has had it's fair share of complications. I think it's fine to chose the annotation route here.
    • the general capability is definitely quite common on Java land, to annotate a "main class" for discovery by a framework.
  • How much effort did you put into your review? A glance, a quick reading, or an in-depth study?
    • Read it through, considered if I could express what I'd use it for using this API (yes), and followed the discussions.

I'm silently hoping for the SwiftPM limitation to be lifted eventually, would be very nice for normal SPM projects to use this as well. :crossed_fingers:

where did it say that it won’t support spm? I may have missed it.

@Lantua

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.

This seems like a good improvement and a feature that will get used. I prefer it over the existing main file behavior and other languages main function. I also love that it elevates 3rd party code to be able to behave like UIKit and AppKit do.

I do wish that Swift had the ability to add multiple entry points. Obj-C’s +load method was actually pretty convenient. But I’m sure some language purist will tell me why that’s a horrible dangerous feature.

1 Like

I hope it will never happen. Static initialisers did far enough harm that not having them in Swift may be considered a feature.

4 Likes

Called it. Bonus points for not including any real life examples of why it’s a bad idea.

A couple immediately off the top of my head:

  • Static initialization contributes to poor application load time because all of that code has to complete before main gets control of execution and the UI can be presented to the user (or whatever the application's core logic is, if it's not a UI app).
  • Execution order of static initializers isn't typically defined across multiple translation units, which makes it risky or fragile to write code where one static initializer uses something that depends on another one having executed already.

Static initialization order fiascos are a recipe for undefined behavior, so it makes sense that Swift and its emphasis on safety would avoid them in favor of a lazy initialization model that avoids this.

9 Likes

I am generally against the proposal, as it introduces a second solution for entry points.

I do like the idea of generalizing entry points and I think that this problem is addressed well enough. However I do not see the problems it solves for avoiding boilerplate code or providing better abstraction.
The boilerplate it currently removes is a single line of code, which will be replaced by the @main attribute, which will not make a big difference.
When providing some abstraction to a user, we implicitly state that the typical user should not be interested in the underlying mechanic. However I disagree that this is the case in this example. I believe that a user will always need to know when his code gets executed. Hence he will always need some knowledge on the underlying mechanic. Additionally I would argue that the line Delegate.main() gives a better indication on the mechanic in a specific application instead of the @main attribute, especially for new users of the language.

One aspect of the Swift language, which I especially love, is the concept of giving the user a clear standard of how to write programs. I believe that this proposal breaks this concept, as it introduces a second alternative of specifying entry points. Until now the simple solution of starting the program at the first line of main.swift was used and multiple concepts were created to support this solution for all kinds of scenarios, such as CommandLine. If we now recognize that this solution does not work for specific scenarios, I would argue that it must be switched out for a different solution. But I don't think that we currently are at this point, as explained above.
Introducing multiple options for specifying the program entry point will make programs in the Swift language less readable and more confusing, especially for new users.

It seems to me that this proposal tries to somehow introduce the concept of a main function, as known from C-based languages, into Swift, while still trying to uphold Swifts own entry point solution. I agree that a main function has its benefits. However I also think that the main.swift solution is a very good and clean alternative, which I often even prefer due to avoiding boilerplate code.

I read the proposal and the thread to this topic.

I do not agree with this statement. I really see the benefit of explicitly calling the main function, as it is a clear way of describing the program's behavior. Otherwise one has to learn about the meaning of those specific attributes, before he or she can understand the program. Maybe you could elaborate a little more on the thought process behind those specific solutions?

As stated above I really like the approach for unifying entry points. I do think that the specific @NSApplicationMain and @UIApplicationMain attributes are an unattractive solution for problems which appeared for AppKit / UIKit. But I would also strongly argue for sticking with a single way of specifying program entry points.

6 Likes

While I can see the benefit of this attribute, I'm against it overall for the following two reasons:

  1. Harder to find entry points. As it stands, being able to search for a main.swift file means that I can easily find the entry point of the program. If the only thing in the file is a call to some struct's static main() method, so be it. I can find that much more easily than it would be to find a @main attribute that is effectively hidden somewhere in the project. (n.b., I think this also applies especially to beginning developers).
  2. Usage in libraries. This could be related to the previous point — I really don't want to have a project that has no visible entry point (even no @main attribute) because there's one hidden in a library (which I think would be feasible under this proposal, if a bad design choice imo). I especially don't want to have two libraries that both have @main attributes — what happens then?

If this pitch is accepted, however, I'd also like to say that I'm not a big fan of @main being the name of the attribute. I think it's unclear and I'd prefer something along the lines of @entryPoint or @applicationMain.


I read the proposal and all the comments on this thread at the time of posting.

2 Likes

Big +1 to this pitch. But please unify all entry point to this single word @main approach. It’s simple and concise.
As to how to find the main method, Xcode should add explicit icon/symbol indicator in specific file and type. E.g. show colorful big @ symbol before the logical main file (@start-from-this-file.swift) and highlight the @main type/method in this file.

1 Like

Just like the compiler does not handle main.swift as an entrypoint in library, it should definitively deny usage of @main in library.

This is not something incompatible with the proposal.

3 Likes

Alright, great. If the proposal is adopted, I think this would be a necessity (it just seemed unclear to me in the proposal itself).

That addresses one of my two main concerns, but I'm still against this proposal overall. I think having a main.swift file in every executable project makes it clear where to look to see how the program starts, and I don't see a way to deal with this problem if this protocol is adopted.

IMHO, that ship has sailed a long time ago, as most Swift projects are iOS and macOS projects, and they don't have a main.swift file.

And I never found it to be an issue.

Contrariwise, I am generally in favour of the proposal because it reduces the number of solutions for entry points from three (or two and a half) to two.

I don’t find the use case for @NSApplicationMain/@UIApplicationMain particularly compelling, but a community-driven effort to remove them would be doomed to failure. I would much rather have a single, extensible implementation of the approach that third-party frameworks (and ArgumentParser) can use than two special cases.

2 Likes

Sorry for the late review.

What is your evaluation of the proposal?

I like the idea of generalizing @NSApplicationMain and @UIApplicationMain. This proposal's approach is different than what we have now however. To me this looks more like a third system for expressing a program's entry point rater than a generalization of the existing attributes.

The way I see it, @NS/UIApplicationMain is attaching a function to a type to bootstrap the program, whereas @main is about using a type's function to bootstrap the program. It's a bit like composition vs. inheritance: both can be used to accomplish the same thing, but composition avoids putting all the responsibilities in the same object.

For instance, in Apple's framework, putting the main in the application delegate sounds a bit funny. If one object should own the main, it should be the application object, not its delegate.

Personally, I'd favor a system that works more like property wrappers. Not only can it express the current @NS/UIApplicationMain attribute exactly, but it also keep the main as a separate function, independent from the type, that can easily be swapped for another when needed. For instance, I can't use @NSApplicationMain in my apps right now because main needs to include MAS receipt checking (and a few other license-related initializations). A wrapper-based approach makes it easy to customize things in a third party library providing its own "main" that supplement the framework's main:

@mainWrapper
func ReceiptCheckingApplicationMain(delegate: NSApplicationDelegate.Type) {
   // receipt checking logic
   NSApplicationMain(delegate)
}

@ReceiptCheckingApplicationMain
class AppDelegate: NSApplicationDelegate { ... }

The proposal's has to say about wrappers that it would force everyone to use a different attribute name and break the consistency. But it enables better composition. The standard library could also define this:

protocol ProgramEntryPoint {
   static func main()
}

@mainWrapper
func Main(entryPoint: EntryPoint.Type) {
   EntryPoint.main()
}

Which would allow users to write this:

@Main
class MyCommandLineTool {
   static func main() { ... }
}

Is the problem being addressed significant enough to warrant a change to Swift?

I think it solves an irritant that is worth solving. Every program has a main after all, but when using a framework you generally use always the same main.

Does this proposal fit well with the feel and direction of Swift?

It could, if it was better following the precedent of @NS/UIApplicationMain. I'd rather avoid adding a third way of doing things. It's not like @main is going to make @NS/UIApplicationMain disappear from existing code bases.

If you have used other languages or libraries with a similar feature, how do you feel that this proposal compares to those?

To me, this inherited default implementation from a protocol feels like Java's static main function within a class. I'm not too sure how I feel about that though.

How much effort did you put into your review? A glance, a quick reading, or an in-depth study?

Participated in the pitch, read the final proposal and the comments in the review thread.

Why not? @main can be used to achieve the exact same effect, even if the implementation details are different. If AppKit/UIKit add the necessary main extensions, @NS/UIApplicationMain could be deprecated aliases for @main when targeting relevant OS releases. (Possibly the wrapper could be emitted into the application for back-deployment too.)

Given that the first response to this thread is from an Apple Swift team developer wanting to delete @NS/UIApplicationMain, it seems very likely this will happen.

2 Likes

@NS/UIApplicationMain won't magically disappear from countless tutorials out there, even if deprecated. But it's true that a deprecation warning, a fix-it in Xcode, and a migration assistant that makes the necessary changes would move things quickly to the new system.

I'm not going to complain if @main gets adopted as proposed; it is a useful thing to have. I mainly wish the actual main it calls would be more composable and less inheritable. It's more an annoyance though, it can be worked in a couple of ways.

Sorry to come late but I wanted to push back on this point specifically. While there is precent for @XXApplicationMain, there is also precedent from main.swift.

To quote from the commonly proposed document:

Swift embraces its C heritage. Where it deviates from other languages in the family, it does so because the feature was thought actively harmful [...] or to reduce needless clutter.

Adding word application reduces neither harm nor clutter. Rather it introduces clutter, and is also out of context when your executable is not an "application", depending on how you define that term.

main is overwhelmingly precedented and familiar from many other languages, and seems to me to be the clear term of art choice for this attribute. It also matches the required function name (unless that also should be called applicationMain which, again, seems like needless clutter to me).

4 Likes