Description
Motivation
In top-level code, Swift behaves differently, defies common user expectations, perform badly, and crashes in inscrutable ways. This leads to confusion and misunderstanding among Swift developers and bad publicity among potential Swift adopters. The differences in Swift top-level code are subtle but important. As new language and performance features are added, the differences increase and become more problematic.
This all comes down to the historical "accident" of pretending that top-level variables are globals, and initializing them in a way that can never be sound.
Modeling variables as globals affects code in utterly surprising ways:
-
Variable initialization semantics change and become difficult to reason about. This can even lead to accessing uninitialized data and runtime crashes with no diagnostic.
-
Compiler diagnostics change: some code magically becomes illegal when moved to the top-level.
-
Performance can radically change in the presence of globals, which defeat many compiler analyses.
Developers naturally learn the semantics and performance characteristics of a language by reducing code to example snippets. When they run this top-level code, they expect to learn how the language implementation behaves in general, carrying those expectations forward. Comparing languages is often done by writing a small benchmark in top-level code.
As new language features are introduced, such as exclusivity and concurrency, top-level code globals interfere with attempts by developers to understand it. For example, global variables change the meaning of formal access, which changes exclusivity diagnostics. Global variables also force expensive runtime calls to execute at each access.
With each improvement to the Swift optimizer, the performance difference between code in the presence of global variables becomes more pronounced.
Proposed solution
Creating an implicit main
function is the solution that is the most flexible and easiest for migration. In this design, top-level variables will be local variables within the main function. The obvious expectation of casual scripters, which makes up the vast majority of Swift scripts, is that script variables are local variables. To expose global variables in the rare case, developers would explicitly specify a visibility modifier. These "true" global variable should then be lazilly initialized exactly like global variables work in libraries.
Alternatives considered
Many of us on the Swift team have wanted this changed for a long time. The informal plan of record since 2016 has been to fix this behavior. Proposed solutions have ranged from:
- disallow top-level code altogether in the next language mode
to - create an implicit 'main' function to encapsulate top-level code
Additional information
Some of the forum threads:
https://forums.swift.org/t/can-anyone-please-explain-this-behavior/4082
https://forums.swift.org/t/compiler-bug-or-feature/24282
https://forums.swift.org/t/calling-global-function-before-declaration/35059
https://forums.swift.org/t/on-the-behavior-of-variables-in-top-level-code/52230
Some of the more recent bug reports:
([GH:#69535] Module functions can access uninitialized main.swift globals
([GH:#70323] Runtime crash when using unsafeUninitializedCapacity incorrectly)
([GH:#70356] Segfault on wrong top-level variable declaration order)
We continually see publicly discussed benchmarks that show Swift in an extremely unfavorable light relative to other languages because benchmarks naturally use top-level code.