Skip to content

Latest commit

 

History

History
212 lines (149 loc) · 33.3 KB

README.md

File metadata and controls

212 lines (149 loc) · 33.3 KB

Swift Guide

This is a little guide for the programming language Swift, about the installation and how to get started with the language. There is also an additional section on versioning of dependent packages and an overview of "dangerous" operations or situations which could cause a program to crash.

All instructions and information are without any guarantee.

Platforms guide

macOS

  • install Xcode
  • a credential manager must be activated for Git if packages from private repositories have to be pulled from the Swift Package Manager; this should not be explicitly necessary within Xcode
  • other development environment than Xcode: see Linux
  • the Swift runtime (Swift standard libraries) is part of the operating system on Apple platforms (this is possible because of the stable ABI already implemented there), so new features (language or standard libraries) may only be available there with new operating system versions and you sometimes have to use #available annotations (for individual components, see also [the official Swift documentation](https ://docs.swift.org/swift-book/documentation/the-swift-programming-language/attributes/)) or (in the configuration for the package manager, for the whole package) with the platforms flag (note: restrictions according to platforms only for platforms mentioned there)

Linux

  • see https://www.swift.org/getting-started or Docker-Images
  • a credential manager must be activated for Git if the Swift Package Manager needs to pull packages from private repositories
  • IDE (integrated development environment): Visual Studio Code with the Swift extension, useful settings for Visual Studio Code see in the section "Suggested settings for Visual Studio Code" below; see this introduction and also VS Code Swift extension lesser known features, there e.g. "Local editing of packages" (important for the simultaneous further development of dependent packages)
  • static linking via addition to the build command --static-swift-stdlib, compare the help output (swift build --help); the compiler setting -Xswiftc -static-executable (link everything) or -Xswiftc -static-stdlib (Swift standard libraries only) that you could add to the build command should be seen as unstable options (none of the options passed via -Xswiftc should be considered stable)
  • note the license questions mentioned under "Windows"

Windows

tl;tr The core requirements for developing Swift programs on Windows are:

  • Visual Studio Installation for enabling native development against the Windows SDK
  • Developer mode to allow symbolic links
  • Admin rights for installing the actual Swift toolchain
  • Visual Studio Code as IDE, see the section on Linux above

Setup and platform-specific hints

  • the instructions on https://www.swift.org/getting-started, section "On Windows" are decisive; the components are listed here again (as of early 2023) with additional comments including the use of an IDE
  • an installation of Visual Studio (as of early 2023: if possible version 2022, but at least version 2019, the Community Edition is sufficient) is required with certain Visual Studio components (Windows 10 SDK and C++ Build Tools); usually, the parallel installation of several Visual Studio versions should be avoided, if necessary execute commands in the command line in the "x64 Native Tools Command Prompt" of the corresponding Visual Studio installation (this generally applies if tools such as Git or Python are used as Visual Studio components have been installed and are not generally accessible in the command line); for license issues see (for Visual Studio 2022) the according documentation
  • note that Git (required for the Swift Package Manager) must be version 2 or higher, an older Git version 1.x is not sufficient
  • a Credential Manager must be enabled for Git if the Swift Package Manager needs to pull packages from private repositories, see e.g. the instructions for GitHub or the instructions for Microsoft Azure
  • note that for the Swift REPL ("Read–eval–print loop”) Python must be available
  • Python is also used to configure the LLVM debugger and is therefore necessary for debugging
  • Python must be available in exactly in the version documented for the Swift version, because a private API of Python is used
  • to simplify some of the installations, it is recommended to use the Windows Package Manager
  • developer mode (you might dispense with it for security reasons, but then you get an according warning while compiling, and you will have problem if some package you require uses symlimks): more precisely: the privilege for setting symbolic links (SeCreateSymbolicLinkPrivilege) is required (and activating developer mode is only one possible way to get there, a restart is needed; for directly setting the required privilege see notes below), see https://learn.microsoft.com/en-us/windows/security/threat-protection/security-policy-settings/create-symbolic-links; Test by using the "mklink" command in the command line (mklink newfile oldfile, creating a symbolic link file named newfile pointing to oldfile)
  • note that the installation of the actual Swift Toolchain requires admin rights, --especially because additions are made to the Visual Studio installation;-- [UPDATE: no changes inside the VS installation is done any more with a current Swift version] other installation parts are installed to %SystemDrive%\Library and to %PROGRAMFILES%\swift, the paths corresponding to %PROGRAMFILES%\swift\icu-69.1\usr\bin, %PROGRAMFILES%\swift\runtime-development\usr\bin, and %SystemDrive%\Library\Developer\Toolchains\unknown-Asserts-development.xctoolchain\usr\bin are added to the PATH environment variable (in the future probably no insertion into the Visual Studio installation necessary any more cf. apple/swift#63887)
  • after an update of Visual Studio it might be necessary to repair the files copied into the Visual Studio installation [UPDATE: not necessary any more with a current Swift version], using the installed toolchain, see how to repair an installation
  • you can build your own installation process for the toolchain as a replacement for the official toolchain installer by using the directories as installed by the installer, setting the appropriate paths, and copying some files into the Visual Studio installation as decribed below (this copying of files into the Visual Studio installation does not have to be repeated for newer versions of Swift)
  • IDE: see Linux; there should only be one Visual Studio installation (as of early 2023), and important tools such as Git must also be accessible outside of Visual Studio's "x64 Native Tools Command Prompt"
  • you should avoid PowerShell on Windows for Swift development (or also in the general case), especially If you are working with a CI pipeline, because there are some hints that this might be problematic (PowerShell is the default for GitHub Windows CI pipelines, configure the pipeline to use cmd or maybe even better bash, bash is available for GitHub Windows CI pipelines)
  • If your program ends unexpextectly, execute echo %ERRORLEVEL% right after the above execution and use the value as argument to the The Microsoft Error Lookup Tool to see what the error is.

files copied by the toolchain installer into the Visual Studio installation [UPDATE: not necessary any more with a current Swift version] (the environment variables UniversalCRTSdkDir, VCToolsInstallDir, and UCRTVersion are set within the x64 Native Tools Command Prompt of Visual Studio1, and SDKROOT would be set to point to %SystemDrive%\Library\Developer\Platforms\Windows.platform\Developer\SDKs\Windows.sdk\usr\share for the standard installation):

copy /Y %SDKROOT%\usr\share\ucrt.modulemap "%UniversalCRTSdkDir%\Include\%UCRTVersion%\ucrt\module.modulemap"
copy /Y %SDKROOT%\usr\share\visualc.modulemap "%VCToolsInstallDir%\include\module.modulemap"
copy /Y %SDKROOT%\usr\share\visualc.apinotes "%VCToolsInstallDir%\include\visualc.apinotes"
copy /Y %SDKROOT%\usr\share\winsdk.modulemap "%UniversalCRTSdkDir%\Include\%UCRTVersion%\um\module.modulemap"

directly setting the SeCreateSymbolicLinkPrivilege / SE_CREATE_SYMBOLIC_LINK privilige: the SE_CREATE_SYMBOLIC_LINK privilege can be set using the gpedit.msc tool (start it via the context menu of the Windows Explorer as administrator); if you have the Home edition of Windows, you first have to get this tool from Microsoft using the following script (open the command line window as administrator):

@echo off 
pushd "%~dp0" 
dir /b %SystemRoot%\servicing\Packages\Microsoft-Windows-GroupPolicy-ClientExtensions-Package~3*.mum >List.txt 
dir /b %SystemRoot%\servicing\Packages\Microsoft-Windows-GroupPolicy-ClientTools-Package~3*.mum >>List.txt 
for /f %%i in ('findstr /i . List.txt 2^>nul') do dism /online /norestart /add-package:"%SystemRoot%\servicing\Packages\%%i" 
pause

Why is Visual Studio necessary?

Question 1: Why is Visual Studio required for Swift?

Answer: This is due to the dependency of some necessary components (the Windows SDK and ucrt have a dependency on the headers and import libraries of VCRUNTIME for development, which are not part of the Microsoft Visual C++ Redistributable).

Question 2: Why isn't this necessary for other natively compiling languages like Rust or Go, but for Swift?

Answer: The special thing about Swift on Windows is that Swift is implemented as a language with "equal rights to C++" so to speak, with direct access to all Windows libraries. The Swift runtime and standard library are implemented using direct calls into the Windows APIs, not emulating things like process startup, allowing a more "bare metal" environment compared to other environments. Swift on Windows is therefore a "true" native solution for Windows programming. This kind of system integration does not exist with Rust and Go.

Distribution of compiled programs under Windows

  • there is (as of early 2023) for Swift programs under Windows no static linking yet, so to run a compiled Swift program, some DLLs must be available separately from the program, as explained below
  • these DLLs are 1) installed in %PROGRAMFILES%\swift, 2) the Visual Studio C++ Redistributables in [VisualStudioFolder]\VC\redist\...\x64\*.CRT, cf. for the latter Microsoft's documentation; the versions of the DLLs must match those with which the Swift program was compiled; Point "2)" can be omitted and instead a corresponding Visual C++ Redistributable- installation are assumed
  • these DLLs must be placed next to the executable or the relevant directories must be named in the PATH environment variable (different versions of the same DLLs in PATH should be avoided)
  • for the DLLs included with such a program, reference should be made to the corresponding licenses or analogous documentation, if necessary, or a corresponding license (recommended: named and documented according to the licensed component) must be enclosed (the latter could e.g. for the Apache 2.0 license, as long as no static linking is done, note the runtime library exception of the Apache 2.0 license); please note a) the Apache 2.0 license for Swift , b) the Unicode License for the International Components for Unicode “ICU”, c) the Documentation from Microsoft on the Visual C++ Runtime Files; Point "c)" can be omitted if (see above) the installation of the corresponding Visual C++ Redistributables is required

Suggested settings and tips for Visual Studio Code

Inlay hints only with keyboard shortcut

Set Editor › Inlay Hints: offUnlessPressed.

Without this setting you see a lot of type hints which makes the Swift code look very ugly at some places.

Detect encoding

Set "files.autoGuessEncoding":true.

Double-click a file in the navigator to keep it open

Unless you set "workbench.editor.enablePreview": false, a file only simply clicked in the navigator will be replaced in the editor pane by the next one clicked. Double-click a file to let it stay in the editor pane until you close it.

Getting started with Swift

Please also consult the official documentation overview and the "open source efforts" section on swift.org.

  • minimal Swift package (using the Swift Package Manager): creation of a new executable Swift package within a directory newly created for the package via swift package init --type executable (without the --type argument or with --type library, a library package is created), a possible minimal program is already created or alternatively consists of the code print("Hello") within a file main.swift, generally you at least use import Foundation; note that a new project created inside Xcode might instead use the Xcode project format which is not useful for Linux or Windows
  • you can also compile a program in the form of a single Swift file via swiftc or use it as a script, on a Mac or iPad you might also use Swift Playgrounds to play around with the language (see there), and there is also the REPL ("Read–eval–print loop”) (to start the REPL under Windows, you need to provide — as of early 2023 — some extra arguments, cf. https://www.swift.org/getting-started)
  • for Swift Package Manager ("SPM") see the Introduction and Documentation or this introductory blog post and the explanations of the versioning concept in the section "Versioning of dependent packages" below, there in particular the section "Tested version combinations" on the question of whether the Package.resolved file should be versioned; get a visual dependencies graph for a package by swift package show-dependencies --format dot | dot -Tsvg -o graph.svg (the dot commands necessitates the installation of graphviz); note that besides getting packages via Git, the SPM will allow to get packages via registry services in the future
  • you might search for packages in the Swift Package Index
  • note that building a Swift package via swift build (called at the top level of the package directory) without further specification uses a quick incremental build and prepares the program for debugging, creating a so-called debug version. While useful during development, a debug version is significantly slower than the so-called release version and also uses more memory; a release version that does without debug additions and for which a whole module optimization is used, among other things, is created with swift build -c release (or with the corresponding setting or the corresponding call in the IDE); the built program is located in the corresponding subdirectory .build/[debug|release] of the package directory
  • if unclear problems arise during the build, it may help to add the verbose specification -v in the build command
  • on platforms other than macOS, tools for the various types of profiling as they come the form of "Instruments" as part of the Xcode installation (e.g. for detecting reference cycles or measuring energy consumption) are (as of early 2023) not yet available; if you want to carry out the relevant analysis, you have to do it under macOS (however, profiling results can vary in principle depending on the platform)
  • the official Swift book: https://docs.swift.org/swift-book/documentation/the-swift-programming-language/, especially important for getting started: Optionals and Optional Chaining and the very useful guard statement in this context, which avoids many if ... else ... constructs (note that the Swift book in the Apple book store is — as of early 2023 — not updated, so better use the mentioned online version)
  • some new language features might first have to be activated via the "enableUpcomingFeature" option before they are activated by default in a newer Swift version (this might be for compatibiliy reasons and is then mentioned on the according evolution page of the feature)
  • platform-specific code (or code specifically for the debug version) can be compiled using the appropriate Compiler Directives (full listing in the Swift documentation)
  • the realization of the complete equality of the standard Swift libraries on different platforms (analogous to ".NET Core" from 2016) starts in 2023, until then it should always be tested (apart from further tests) whether a Swift program compiles for all target platforms
  • you can develop for Linux on a non-Linux system in Visual Studio Code Dev Containers, see the description of how this works with Swift
  • documentation in code according to DocC; include the --include-extended-types option to also document extensions to types from other modules (swift package generate-documentation --include-extended-types); on Windows with Swift version 5.9.1 (create: swift package generate-documentation --output-path docs --transform-for-static-hosting --target ..., show: python -m http.server -d docs)
  • for executing the unit tests of a Swift package, use the tests tab in Xcode or the test icon in Visual Studio Code (on the left) or in the command line e.g. swift test --parallel --xunit-output test-log.xml (as of early 2023, the --parallel argument is necessary for --xunit-output to work)
  • also consult the platform-specific hints in the section "Platforms guide" above, where amongst other things the #available annotations and the platforms flag are mentioned
  • you may also consult the section below about comparing Swift against Java and C# regarding “problematic” events
  • for optimizations see "Writing High-Performance Swift Code"
  • for benchmarking, you might consider this package

Versioning of dependent packages

This section describes how the Swift Package Manager proceeds, its procedure corresponds to a common practice elsewhere. A procedure that makes sense in this context is explained.

See also the relevant Swift Package Manager documentation: "Building Swift Packages or Apps that Use Them in Continuous Integration Workflows".

Explanation "semantic versioning"

  • With "semantic versioning" there is a major, minor and patch version, e.g. "2.4.12" = major: 2, minor: 4, patch: 12, with the following interpretation:
  • Patches only fix bugs or optimize (or maybe do code refactoring) but don't bring any changed or new features.
  • New minor versions only extend functionalities, but remain backwards compatible.
  • New major versions may contain breaking changes.

Formulation of dependent packages with semantic versioning

  • A program or library uses other packages.
  • A minimum version is usually specified for each of these.
  • The Package Manager then fetches it (using the command "swift package update" or building without an existing "Package.resolved" file, see below) a version that is as new as possible within the same major version.
  • You can refine this information, e.g. only get new patches, but no new minor version.

Why setting minimal versions makes sense

  • Newer but still suitable versions of packages are automatically used (when updating the packages, see above). These may contain important bug fixes.
  • A consistent statement of precise version numbers would be extremely difficult to handle in practice. Reason why a general use of precise version numbers is not practical: The package versions must be compared between all packages used, so the same packages may be drawn from several packages used. Example: The program requires Package A and Package B, and both Package A and Package B require Package C. But Package A may require a slightly newer version of Package C than Package B requires. This means that the package manager gets a number of version conditions and has to find a solution (or issue an appropriate error message). If you would generally only specify precise versions for all packages (over several levels!), then that would be difficult to handle. In other words: The package manager will then often not be able to find a solution. It should be remembered that packages from other sources are also commonly used (however "official" they may be).

Tested version combinations

  • Normally, as a "valid" program, the sources of a program are not passed on, but rather a CI pipeline is built with a) automatic fetching of the latest packages (via "swift package update"), if necessary, and b) automatically tested executables. This is the usual practice, and the quick fetching and testing of all new fixes in their combination ("swift package update") the heart of Continuous Integration.
  • What you can (and should) do additionally: For the successfully tested executables, note the precise package versions drawn for them. This information is in the "Package.resolved" file, which is often excluded from versioning. You should definitely remember these combinations for productively used executables (in the form of the “Package.resolved” file).
  • This precise version information can be used to get exactly this combination (via the set tag) again when building later (build with given "Package.resolved" file or just fetch the packages via "swift package resolve" and note whether the "Package.resolved" file changes).
  • A note can be made by including the "Package.resolved" file in the versioning (a new package created via "swift package init [--type executable]" does not exclude the "Package.resolved" file from versioning), a correspondingly tagged release version of the source code thus contains the specification of the package combination used. A build via swift build [-c release] should not change the "Package.resolved" file (the package versions listed there could then be used), the command git status -s should therefore have an empty return.
  • Fixed version numbers in the form of a versioned Package.resolved is only an indication of a working version combination, so to speak as a fallback if the actual version restrictions (in Package.swift) are not sufficient (because semantic versioning is not always perfect in practice). Actual builds that you use might come from a CI pipeline which updates packages freely.

Concurrent development of dependent packages

Comparing Swift against Java and C# regarding “problematic” events

tl;dr Swift tries to be safe (you might say “harmless” i.e. avoiding dangerous operations, e.g. avoiding null pointer exceptions) while also being very efficient and guaranteeing correct results. An exception mechanism of the same kind as in Java or C# (where you can catch almost any error) would be difficult in Swift as Swift (on purpose2) does not use a tracing garbage collector (so discarding part of the stack would leave you with memory leaks). So instead of silently failing (e.g. letting numbers overflow silently as in Java and C#) and giving wrong results, the philosophy is that it is better to let the whole process fail in cases where other solutions would be overly inefficient or overly complex. As a guideline, think “Swift is mostly harmless” and learn the patterns necessary to write robust code.3

This is an overview of “dangerous” operations or situations which could cause a Swift program to crash, in comparison to Java and C#. (Note that even if your program does not crash, your program still might do unexpected things e.g. because of an unnoticed overflow of a number value.)

  • None of these systems can handle problems within a program caused by excessive “memory hunger” (or too low memory on the system), aborts because of a high recursion level (or generally a “stack overflow”) or problems in the virtual machine or corresponding problems caused by compiler errors.
  • Apart from these points, “everything” can be intercepted (“catched”) in “managed code” systems such as Java or C#.
  • However, neither Java nor C# enforces catching all possible explicitly thrown exceptions (there are so-called “unchecked” exceptions whose handling is not enforced), and arithmetic operations or poorly implemented standard library APIs can throw implicit errors. A general try/catch wrapping must then be carried out for each job, for example.
  • In Swift, such a general wrapping in try/catch (in Swift this is actually do/catch) “to catch everything” as in Java or C# is not possible, but only a few “dangerous” operations (e.g. forced unwrapping of optionals) or easily recognizable “unsafe” constructions or functions (e.g. UnsafeMutableRawPointer) have to be dispensed with or used in the usual "sensible" way (e.g. for substrings, only use ranges you previously found in the same string) in order to achieve the same situation as for Java or C# where you apply a general try/catch (because there are no unchecked exceptions in Swift). Apart from the dangers of overflow or underflow of numbers (these are often overlooked, especially since the according standard behavior differs from Java and C#, see the following table), the programmer is usually aware of the danger of these operations and they are then at best not used or only used very cautiously (possible overflows or underflows of numbers can also be handled, cf. the same table). However (as of early 2023) no automatic check for such dangerous operations by the compiler is possible.

Details:

“☠” denotes a “dangerous” Swift operation in the following table.

Event Java C# Swift
problem caused by virtual machine errors or compiler errors uncatchable4 uncatchable uncatchable
too low memory on the system uncatchable uncatchable uncatchable
excessive “memory hunger” while enough memory on the system (or paging) uncatchable crash5 uncatchable uncatchable, no crash
stack overflow uncatchable uncatchable uncatchable
explicitly thrown exception handling of a “checked” exception is enforced by the compiler, but not of an “unchecked” exception (results in a crash if unhandled) handling of a “checked” exception is enforced by the compiler, but not of an “unchecked” exception (results in a crash if unhandled) there are only “checked” exceptions
null pointer exception unchecked Exception, but catchable unchecked Exception, but catchable only possible if non-null assumption is explicitly enforced (“forced unwrapping” of an optional) ☠6, then uncatchable
floating point division by 0 no error (result: infinite) no error (result: infinite) no error (result: infinity)7
integer division by 0 catchable (unchecked) catchable (unchecked) uncatchable ☠
number over-/underflow no error (operators are “overflow operators”) no error (operators are “overflow operators”) uncatchable error ☠, but overflow operators (with prefix "&") and controlled operations (e.g. addingReportingOverflow) available7
array index not allowed8 catchable (unchecked) catchable (unchecked) cannot be caught ☠ (same for any Collection)
problems analogous to the array index problem caused by poorly formulated APIs yes, even as unchecked exceptions, but catchable yes, even as unchecked exceptions, but catchable in general not present (corresponding operations return optional values)
unsafe9 operations special case special case corresponding constructions and functions are easily recognizable as unsafe ☠ by naming conventions (e.g. UnsafeMutableRawPointer), problems cannot be caught

Footnotes

  1. The corresponding values can be extracted from the registry entries HKLM\SOFTWARE\[Wow6432Node]\Microsoft\Microsoft SDKs\Windows\v10.0\InstallationFolder, HKLM\SOFTWARE\[Wow6432Node]\Microsoft\Microsoft SDKs\Windows\v10.0\ProductVersion, and HKLM\SOFTWARE\Microsoft\Windows Kits\Installed Roots\KitsRoot10, cf. the installer code.

  2. Swift uses an optimized reference counting instead of a tracing garbage collector which results in significantly less memory used and also makes a Swift program deterministic, avoiding the non-reproducibility of some errors you might encounter when using a tracing garbage collector. Swift might even be used for "close to the metal" situations.

  3. You may disagree with the design choices of the Swift language creators, but their belief is that those design choices result in better software at the end.

  4. “Catchable” means that a crash can be prevented using try/catch (or in Swift: do/catch).

  5. “Crash” can mean the termination of the program run by a virtual machine with a corresponding message; in any case, the program is aborted.

  6. Force-unwrapping an optional might be a sign that the code is mal-constructed, but there are sensible uses like implicitly unwrapped optional property for inhertance reasons.

  7. Behavior regarding arithmetic can additionally be changed using compiler flags (this may result in IEEE conformity being broken). 2

  8. Index access is generally to be replaced with other methods; avoiding such errors by the use of dependent types is currently not possible in any of the systems mentioned; with Swift, index access can be made an unsafe9 operation using a compiler flag.

  9. Definition: “unsafe” operations have an undefined behavior for some inputs, example: "pointer arithmetic". 2