SE-0390: @noncopyable structs and enums

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).

5 Likes