The main.swift special file and the entry points
Table of Contents
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:
We obtain the following two errors:
and
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
.
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:
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:
But then again, in another module it won’t work:
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.
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.