@main: Type-Based Program Execution

I think I made the same concern during a review for one of these. The problem is each one of these is like a protocol, but have a quirk that protocols don't cover. If the language was more mutable, then we could develop a superior protocol concept that dC, dMA, and pW could template, and therefore could be removed as built-ins. We need to start with generalizing protocols further.

I'm not sure if this is what you mean, but let me turn this into a question: what happens when this attribute is applied to a protocol? Do conforming types automatically become "main like"? If so, this could be very nice.

The attribute is only applicable to types — it would be an error to apply @main to a protocol. A protocol can—and would, in basically all cases I can think of—supply the static func main() requirement for the type that is declared with @main. As the proposal describes, the compiler enforces the rule that only a single type is the designated entry-point by making it an error to apply @main to more than one type.

Ok, why not allow the attribute on a protocol? Of course, only one type could conform to the protocol in a program, but that seems like a natural way to provide the default implementation. I don't see why it is important to have the attribute here:

@main
struct X : SomeObviouslyMainlikeProtocol {

instead of on the protocol itself.

In any case, if this is a bad idea, it would be great to mention it in the alternatives considered section of the proposal. Thanks!

3 Likes

@Chris_Lattner3’s last point is what I was thinking the entire time reading through this thread. Unless there are reasons why this Is not a good idea, it seems more natural to encapsulate all of the required functionality to get a framework up and running into a protocol defined by the framework.

Despite that, I’m not sure how I feel about the pitch overall.

I do support the idea of this pitch when it comes to generalizing the existing @UIApplicationMain and @NSApplicationMain attribute into something useful for other frameworks, but I think making the behavior automatic when conforming to a protocol goes a bit too far.

Take NSApplicationDelegate for instance. If the protocol had the @main attribute, it makes it impossible to create an application delegate that doesn't have its own main. That'd be a bit problematic if you are implementing main in a separate file (maybe you want to write main in Objective-C). Or should we add a way to "un-main" your type?

Honestly, I'm annoyed at the idea that conforming to a protocol would emit a main by itself. That this happens isn't obvious at all. It's hard to opt-out, and the way you can "override and call the super implementation" is pretty awkward.

3 Likes

This is a good explanation of why I don’t think allowing @main on protocols is the direction we want. @Chris_Lattner3 I’ll add a note to the proposal on this point!

1 Like

+1 thanks Nate. FWIW, people felt the same way about dynamicMemberLookup (requiring an attribute on the class) but that got backpeddled later.

The difference I see between dynamicLookup and main is that the former is a localized API property of the type with a dynamicMember subscript, whereas main is a global property, and there can only be one. It doesn't seem unreasonable that a large program might have multiple *ApplicationDelegate classes for some reason, with only one being the entry point, or that a tools framework like LLVM might have multiple entry-point-like classes that serve as entry points for different tools, so having protocol conformance automatically occupy the one available entry point slot seems undesirable.

5 Likes

Are you saying that it seems the API is clumsy because something that shouldn’t be possible is? Or are you saying that something that should be possible is awkward and roundabout to accomplish?

(In my mind, the ability to “override and call super” is a useful feature. It’s why I find myself still using NSApplicationMain(_:_:) instead of @NSApplicationMain.)

I meant that if you're shadowing main() with our own main() inside a type that conforms to the protocol, you calling the protocol's extension method must be done this way:

(self as NSApplicationDelegate.Type).main()

That'd be the equivalent of calling NSApplicationMain(_:_:), except it's a lot less clear what this is.

1 Like

And if the protocol has an associated type...?

Yep, that makes sense to me, I agree that the global effect is different. I was just asking that this get captured in the proposal.

-Chris

2 Likes

@compnerd — I took a look at this previous thread on entry points so that I could understand the different things at play, and as you describe there, there seem to be two dimensions of platform-specific differences at work.

On the one hand, there's a difference in the kinds of arguments that are available to an entry point, like the typical argument count and vector, the Windows-specific HINSTACE, and the auxiliary vector on Linux. Some of these we provide right now as globally available properties through the stdlib's CommandLine type, and the proposal calls out a future direction of letting libraries define main as a (argc: Int, argv: ...) -> Int function instead of a nullary one. So for the other platform-specific values we could provide access to them in one, or preferably both, of those locations.

On the other hand, Windows apps have a distinction between how GUI and console apps are launched based on whether you provide a wmain or wWinMain entry point, which isn't specifically addressed in the proposal. I see a couple different ways we could go about handling this:

  1. We could expand the allowed static methods that a type can provide to include windowsMain() and/or windowsMain(argc: Int, argv: ...). If the @main designated type provides one of these, then the compiler would generate a wWinMain entry point instead of the default wmain.

  2. We could allow parameters on the @main attribute, so that you could write something like this, which would look for a main(console: Bool) or main(console: Bool, argc: Int, argv: ...) method:

    @main(console: false)
    struct MyApp: Application {
         // ...
    }
    

If the libraries that provide @main-supporting application-root protocols generally support either GUI or console apps, but not really both, #1 would seem like an appropriate solution. If we anticipate that libraries will want to support both kinds of development (which seems unlikely to me), then #2 might make more sense. #1 would still work in the second case, however — the library would only need to supply different protocols for console and GUI apps.

One issue with #2 is that it would look like things are more flexible than they are, since this is borrowing a bit of the way property wrappers work with wrappedValue. It would be a further extension of this syntax to support arbitrary library-defined parameters on the @main attribute.

What do you think of these as potential future extensions of the @main approach? Is this a correct summary of the platform-specific issues we need to address?

If we were to eventually adopt extension #1, you could add the following extension to your swift-win32 library. Then you could use the plain @main attribute on your SwiftApplicationDelegate type in your HelloSwift example.

extension AppDelegate {
    public static func windowsMain(
        _ argc: Int32,
        _ argv: UnsafeMutablePointer<UnsafeMutablePointer<Int8>?>
    ) -> Int32 {
        ApplicationMain(argc, argv, nil, self)
    }
}

Thanks @nnnnnnnn for following up on this.

I like the two separate domains of concerns that you have drawn, and is similar to how I am thinking about this problem space as well and I think that it is covering the areas that I wanted to really have considerations for, so in terms of your question of is this a correct summary: yes.

I think that the differences lie in the implementation detail only, and so, lets take each point individually about that.

The differences in arguments (Signature)

I think that the part that I am getting tripped up on here, which is probably my own misunderstanding, is what controls this signature. I would like that made a bit more explicit in the proposal.

My opinion is that we want to remove this from the compiler and instead place it into the purview of libraries. This allows us to have the ability to easily control the signature within frameworks. Doing so means that if it were deemed useful for a particular class of applications that the desired signature on Linux is void (int, char **, char **, void *) for example, then the framework does so without any impact to the the rest of the platforms.

The benefit is that this scales nicely with freestanding (embedded) environments exposing a void (void) to even android which actually would need something completely custom because the JNI setup and teardown must occur around this setup.

UI vs Console (Subsystem)

Windows has a different well defined signature - wWinMain for UI applications. However, android has a separate issue where the entry point must be written in C with a stub to ensure that the JVM is initialized before main. This is another case where the console application and the UI application startup behaviour is different.

If the entry point is controllable by the (executable) module being built, I think that we could get away with even just @main or @ApplicationMain.

I do think that providing the possibility of a single module providing both the console and the UI development is really interesting - the only caveat would be that only a single one would be active at any given time. The idea is similar to how the Microsoft C Runtime allows for both the console and the UI application entry points based upon the symbols you use. A similar distinction here would be convenient from the user perspective of, I want to experiment with an application and I can progress from it being a console to a UI application.

I'm left wondering if we could optionally just take a parameter (or a pair) which tells us what the signature and spelling of the entry point is. The invoked method would be required to have that same signature. By making it optional, I hope that most people would not even have to consider it when writing their applications.

I hope that is somewhat coherent.

Thanks,
Saleem

To keep the user-facing language feature uncluttered, maybe the attribute could live on the main method implementation, so you could have something like:

// In the framework code
extension WindowsUIAppDelegate {
  @entryPoint(symbolName: "wWinMain", convention: stdcall)
  static func main(_ hInstance: HINSTANCE, _ hPrevInstance: HINSTANCE, lpCmdLine: LPWSTR, _ nCmdShow: Int32) { ... }
}

// In the user code
@main
class MyAppDelegate: WindowsUIAppDelegate { ... }
6 Likes

@Joe_Groff, interesting idea, I don't have a strong opinion on whether we split it this way or keep it together.

The proposal is deliberately a bit hand-wavy on this point, since it will be up to a future proposal to determine the exact mechanism that we use here. I think the options that we've seen so far for both issues are to have a compiler-blessed set of entry point methods, or to use an attribute like Joe's @entryPoint suggestion to designate a main method as having a specific symbol name. I really like that suggestion, which does let us keep both the entry point implementation and information about which signature it's using entirely in the library.

I'll include a summary of all this in the revised proposal — thanks!

Thanks @nnnnnnnn. I think that we should still record the original suggestion as well that @Joe_Groff improved upon - having the option to just have those options (with default values) be part of the @main attribute.

Using the attributes for the entry point do solve the issue and reduce the need to to modify the compiler to support new environments (that is make this more flexible). It also impacts the signature that @main sees, so I think that this should be part of the pitch.

Do you think you could include an example of an alternate signature in the documentation to help illustrate the usage?

Thinking about it more, I like the idea of an open-ended @entryPoint attribute that can specify an arbitrary symbol name, function signature, and calling convention, because it keeps the language design distinct from any platform-specific details, and also potentially makes the feature usable for non-standard entry points such as for plugins, or other past or future platforms with their own idiosyncratic process entry point behavior.

3 Likes
Terms of Service

Privacy Policy

Cookie Policy