Skip to main content
  1. Posts/

The main.swift special file and the entry points

·790 words·4 mins

Last weekend had started as always: I wanted to learn something new. And that day I wanted to discover console applications in Swift. I’ve read a lot of interesting articles on the subject and I’ll be sure to tell you about it in my own articles very soon.

If you have already done iOS or macOS development you already know that the entry point is the AppDelegate or the SceneDelegate.

But what about a console application?

Well, it’s simply the main.swift file. But this one has some particularities. Let’s have a look at it.

The basic problem #

When I created my Swift Package project for my console application, I naturally created a Main.swift file respecting the file naming nomenclature (with capitalization).

I write my code inside :

import Foundation
import ConsoleKit

let console: Console = Terminal()
let input = CommandInput(arguments: CommandLine.arguments)
let context = CommandContext(console: console, input: input)

var commands = Commands(enableAutocomplete: true)
commands.use(AboutCommand(), as: AboutCommand.name, isDefault: true)

do {
    let group = commands.group(help: "Test")
    try console.run(group, input: input)
}
catch {
    console.error("\(error)")
    exit(1)
}

But during the compilation it does not go well:

Compilation error

We obtain the following two errors:

Expressions are not allowed at the top level

and

Statements are not allowed at the top level

But why?

For those who don’t want to wait until the end, it’s because the Main.swift file has a capital letter and not a lower case as expected: main.swift.

You will have the same problem if you name your file something other than main.swift.

Now let’s see why. According to the Apple documentation that can be found here we have the following sentence:

Top-level code is not allowed in most of your Swift source files. For clarity, any executable statement not written within a function body, within a class, or otherwise encapsulated is considered top-level. We have this rule because if top-level code were allowed in all your files, it would be hard to determine where to start the program.

The reason is understandable and logical.

The case of a Playground file #

Note that in a playground within XCode there is no problem to put this type of code, even if your playground is not called main.

Top-level code on playground

This case is also explained in the previous post:

Because playground files do support the execution of top-level code. Code within a playground file is order-dependent, run in top-down lexical order. For example, you can’t use a type before you define it.

Everything depends on whether the files of your project are dependent on an order or not. Note on the other hand that if you add source files to your playground, they will not be able to have top-level code:

Top-level code on non main file

The case of Swift playground #

For Swift playground the behavior is identical to an XCode playground file.

As long as your top-level code is present in the Main part of your playground there will be no problem:

Top-level on main file

But then again, in another module it won’t work:

Top-level on non main file

Le cas des fichiers script Swift #

You probably know this but you can directly execute Swift code from your terminal by adding the following code to your :

#!/usr/bin/xcrun swift

or by calling the following command:

xcrun swift file.swift

For this type of case it is quite possible to use top-level code because it is a script. The execution is simple, the entry point is the launched file.

@main #

Since Swift 5.3 you may have noticed that a new annotation exists:

import UIKit

@main
class AppDelegate: UIResponder, UIApplicationDelegate { 
    ... 
}

This annotation forces the class or structure that has it to have a static main() method. This method will be called automatically when the application is started.

@main error

This avoids having a main.swift file and/or top-level code. Quite handy! This means that my base code can evolve and use @main:

import Foundation
import ConsoleKit

@main
class Application {

    static func main() {

        let console: Console = Terminal()
        let input = CommandInput(arguments: CommandLine.arguments)
        let context = CommandContext(console: console, input: input)

        var commands = Commands(enableAutocomplete: true)
        commands.use(AboutCommand(), as: AboutCommand.name, isDefault: true)

        do {
            let group = commands.group(help: "Test")
            try console.run(group, input: input)
        }
        catch {
            console.error("\(error)")
            exit(1)
        }

    }

}

It is interesting to note that the compiler will always check that you have only one possible entry point and that it is done via a main.swift file or via the @main decorator.

This new annotation is the direct descendant of @UIApplicationMain and @NSApplicationMain.

But don’t be mistaken, whether it is @main, @UIApplicationMain or @NSApplicationMain it is only syntactic sugar that will allow your compiler to generate the main.swift file for you.

Sources #