I can think of several cases where having linear types would be very helpful in Swift.
- Linear types would enable the author of a type to ensure that instances of a type are always destroyed on the main actor. This is especially helpful when working with UIKit, where almost everything must be called on the main actor.
@MainActor
@noncopyable
struct OrientationObserver {
let cancellable: AnyCancellable
public init(
onOrientationChanged orientationChanged: @escaping (_ newOrientation: UIDeviceOrientation) -> Void
) {
cancellable = NotificationCenter.default
.publisher(for: UIDevice.orientationDidChangeNotification)
.sink { _ in
orientationChanged(UIDevice.current.orientation)
}
UIDevice.current.beginGeneratingDeviceOrientationNotifications()
}
public consuming func cancel() {
UIDevice.current.endGeneratingDeviceOrientationNotifications()
}
fileprivate deinit {}
}
Plenty of people in these threads have stated that they would find the ability to ensure destruction on the main actor useful:
- Linear types could be used to implement a safe continuation type without runtime checks:
public func withContinuation<T>(_ fn: (consume Continuation<T, Never>) -> Void) async -> T {
return await withUnsafeContinuation {
fn(Continuation($0))
}
}
public func withThrowingContinuation<T>(_ fn: (consume Continuation<T, Error>) -> Void) async throws -> T {
return try await withUnsafeThrowingContinuation {
fn(Continuation($0))
}
}
@noncopyable
public struct Continuation<T, E: Error> {
let unsafeContinuation: UnsafeContinuation<T, E>
fileprivate init(_ unsafeContinuation: UnsafeContinuation<T, E>) {
self.unsafeContinuation = unsafeContinuation
}
public consuming func resume() where T == Void {
unsafeContinuation.resume()
}
public consuming func resume(returning value: T) where E == Never {
unsafeContinuation.resume(returning: value)
}
public consuming func resume(returning value: T) {
unsafeContinuation.resume(returning: value)
}
public consuming func resume(throwing error: E) {
unsafeContinuation.resume(throwing: error)
}
public consuming func resume(with result: Result<T, E>) {
unsafeContinuation.resume(with: result)
}
public consuming func resume<Er>(with result: Result<T, Er>) where E == Error, Er : Error {
unsafeContinuation.resume(with: result)
}
fileprivate deinit {}
}
- Linear types would be helpful for actors that would like to run isolated code upon destruction
- Linear types would be helpful for types that would like to run asynchronous code upon destruction
- Linear types would be helpful for types where the destructor can fail/throw
- Linear types would be helpful for types where destruction can be expensive/blocking and thus want to make destruction explicit
Linear types aren't "necessary" — you can make working code without them — but Swift is full of helpful tools that aren't "necessary", but help ensure your code is correct (automatic reference counting, explicit nullability, enumerations, noncopyable types (soon), and even structures).