Swift has a formal process for proposing and accepting changes to the language, known as the Swift Evolution Process which covers all language features, standard library, compiler configuration, and package manager.
There are 11 proposals implemented and assigned to Swift 5.8, released since Xcode 14.3, including many features like back deployed, collection downcasts in cast patterns, improved unsafe pointer family, usage of implicit self
after unwrapped, etc.
Explicit self
has historically been required in closures, in order to help prevent users from inadvertently creating retain cycles. As of Swift 5.3, implicit self
is permitted in closures when self
is written explicitly in the capture list.
This feature extends support to weak self
captures, and permit implicit self
as long as self
has been unwrapped.
class FooViewController {
let button: Button
func dismiss() {}
func setup() {
button.tapHandler = { [weak self] in
guard let self else { return }
dismiss()
}
}
}
Like with implicit self
for strong
and unowned
captures, the compiler will synthesize an implicit self.
for calls to properties / methods on self
inside a closure that uses weak self
.
This feature introduces two related changes to make it easier to adopt new attributes in existing code:
#if
checks to surround attributes on a declaration wherever they appear, eliminating the need to clone a declaration just to adopt a new attribute.hasAttribute(AttributeName)
that evalutes true
when the compiler has support for the attribute with the name AttributeName in the current language mode.#if hasAttribute(preconcurrency)
@preconcurrency
#endif
protocol P: Sendable {
func f()
func g()
}
Conditional directive hasAttribute
only considers attributes that are part of the language, custom attributes (property wrappers, result builders, and global actors) will evaluate false
.
A conditionally-compiled branch based on #if hasAttribute(UnknownAttributeName)
can still be parsed by an existing compiler, even though it will not be applied to the declaration because it isn’t understood.
Currently, passing a keypath to print(), yields the standard output for a Swift class. This is not very useful.
struct Theme {
var backgroundColor: Color
var foregroundColor: Color
var overlay: Color {
backgroundColor.withAlpha(0.8)
}
}
print(\Theme.backgroundColor)
would have an output of roughly Swift.KeyPath<Theme, Color>
, which doesn’t allow foregroundColor
to be distinguished from any other property on Theme
.
Swift 5.8 takes advantage of whatever information is available in the binary to implement the debugDescription requirement of CustomDebugStringConvertible
.
print(\Theme.backgroundColor) // outputs "\Theme.<offset 0 (Color)>"
print(\Theme.overlay) // outputs \Theme.<computed 0xABCDEFG (Color)>
This feature rounds out initialization functionality for every relevant member of UnsafeMutablePointer family:
- UnsafeMutablePointer
- UnsafeMutableRawPointer
- UnsafeMutableBufferPointer
- UnsafeMutableRawBufferPointer
- Slice<UnsafeMutableBufferPointer>
- Slice<UnsafeMutableRawBufferPointer>
The functionality will allow managing initialization state in a much greater variety of situations, including easier handling of partially-initialized buffers.
Swift’s sorting algorithm was changed to be stable before Swift 5. Since all current versions of the Swift runtime include a stable sort (which was introduced before ABI stability), this change can be made to the standard library documentation.
A stable sort is a sort that keeps the original relative order for any elements that compare as equal or unordered. For example, given this list of players that are already sorted by last name, a sort by first name preserves the original order of the two players named “Ashley”:
var roster = [
Player(first: "Sam", last: "Coffey"),
Player(first: "Ashley", last: "Hatch"),
Player(first: "Kristie", last: "Mewis"),
Player(first: "Ashley", last: "Sanchez"),
Player(first: "Sophia", last: "Smith"),
]
roster.sort(by: { $0.first < $1.first })
// roster == [
// Player(first: "Ashley", last: "Hatch"),
// Player(first: "Ashley", last: "Sanchez"),
// Player(first: "Kristie", last: "Mewis"),
// Player(first: "Sam", last: "Coffey"),
// Player(first: "Sophia", last: "Smith"),
// ]
Types outside of the standard library which conform to ExpressibleByIntegerLiteral
are restricted in practice in how large of a literal value they can be built with, because the value passed to init(integerLiteral:)
must be of a type supported by the standard library.
Swift 5.8 adds a new type to the standard library called StaticBigInt
which is capable of expressing any integer value. This can be used as the associated type of an ExpressibleByIntegerLiteral
conformance.
extension UInt256: ExpressibleByIntegerLiteral {
public init(integerLiteral value: StaticBigInt) {
precondition(
value.signum() >= 0 && value.bitWidth <= Self.bitWidth + 1,
"integer literal '\(value)' overflows when stored into '\(Self.self)'"
)
self.words = Words()
for wordIndex in 0..<Words.count {
self.words[wordIndex] = value[wordIndex]
}
}
}
Add a @backDeployed(before: ...)
attribute to Swift that can be used to indicate that a copy of the function should be emitted into the client to be used at runtime when executing on an OS prior to the version identified with the before:
argument.
struct Foo {
@available(iOS 12.0, *)
@backDeployed(before: iOS 16.0)
public func bar() {}
}
The @backDeployed
attribute may apply to functions, methods, and subscripts. Properties may also have the attribute as long as the they do not have storage. The compiler detects applications of back deployed function and generates code to automatically handle the potentially runtime unavailability of the API.
Swift 5.7 has a limitation that prevents the opening of an existential argument when the corresponding parameter is optional. This release changes that behavior, so that such a call will succeed when a (non-optional) existential argument is passed to a parameter of optional type:
func openOptional<T: P>(_ value: T?) { }
func testOpenToOptional(p: any P) {
openOptional(p) // okay, opens 'p' and binds 'T' to its underlying type
}
The implementation of the result builder transform, introduced in Swift 5.4, places a number of limitations on local variable declarations in the transformed function. Specifically, local variables need to have an initializer expression, they cannot be computed, they cannot have observers, and they cannot have attached property wrappers. This release lifts all those limitations.
import SwiftUI
struct ContentView: View {
var body: some View {
GeometryReader { proxy in
@Clamped(10...100) var width = proxy.size.width
Text("\(width)")
}
}
}
This release changes the string that magic identifier #file
evaluates to — instead of evaluating to the full path, it will now have the format <module-name>/<file-name>
.
For those applications which still need a full path, there will be a new magic identifier, #filePath
. Both of these features will otherwise behave the same as the old #file
, including capturing the call site location when used in default arguments. The standard library’s assertion and error functions will continue to use #file
.
With this feature, a file at /Users/becca/Desktop/0274-magic-file.swift
in a module named MagicFile
with this content:
print(#file)
print(#filePath)
fatalError("Something bad happened!")
Would produce this output:
MagicFile/0274-magic-file.swift
/Users/becca/Desktop/0274-magic-file.swift
Fatal error: Something bad happened!: file MagicFile/0274-magic-file.swift, line 3
This feature introduces a compiler flag -enable-upcoming-feature X
, where X is a name for the feature to enable, with naming convention as UpperCamelCaseFeatureName. Each proposal will document what X is, so it’s clear how to enable that feature.
For example, SE-0274 could use ConciseMagicFile, so that -enable-upcoming-feature ConciseMagicFile
will enable that change in semantics.
You can pass multiple -enable-upcoming-feature
flags to the compiler to enable multiple features.