Skip to content

eraseToStream and eraseToThrowingStream unable to handle non-sendable Elements #42

@BrentMifsud

Description

@BrentMifsud

Description

Something I noticed today when using NotificationCenter.notifications is that I cant actually use eraseToStream to type erase because Notification does not conform to sendable.

Apple recommends using a .map or .compactMap to take out the value that you need to send, however I noticed that even if you have stripped out the non-sendable type via a map or compact map, eraseToStream still produces errors in swift 6 and warnings in swift 5

Here is an example of observing background refresh status changes:

image

    let notifications = notificationCenter
            .notifications(named: UIApplication.backgroundRefreshStatusDidChangeNotification)
            .compactMap { notification -> UIBackgroundRefreshStatus? in
                guard let application = notification.object as? UIApplication else {
                    return nil
                }

                return await application.backgroundRefreshStatus
            }
            .eraseToStream()

The only workaround I have found is to do something like this (rather than using eraseToStream)

        let (notificationUpdates, continuation) = AsyncStream<UIBackgroundRefreshStatus>.makeStream()

        let task = Task { [notificationCenter, application] in
            let notifications = notificationCenter
                .notifications(named: UIApplication.backgroundRefreshStatusDidChangeNotification)
                .compactMap { notification -> UIBackgroundRefreshStatus? in
                    guard let application = notification.object as? UIApplication else {
                        return nil
                    }

                    return await application.backgroundRefreshStatus
                }

            continuation.yield(application.backgroundRefreshStatus)

            for await backgroundRefreshStatus in notifications {
                continuation.yield(backgroundRefreshStatus)
            }
        }

        // this might not be necessary
        continuation.onTermination = { _ in
            task.cancel()
        }

Checklist

  • If possible, I've reproduced the issue using the main branch of this package.
  • This issue hasn't been addressed in an existing GitHub issue or discussion.

Expected behavior

Since AsyncStream and AsyncThrowingStream are capable of handling non-sendable elements, I believe eraseToStream and eraseToThrowingStream should mirror this behavior.

image

Actual behavior

I get a compiler warning (in swift 5) and an error (in swift 6) when using eraseTo(Throwing)Stream on a sequence with non-sendable elements.

Steps to reproduce

You can reproduce it with this code snippet:

    let notifications = notificationCenter
            .notifications(named: UIApplication.backgroundRefreshStatusDidChangeNotification)
            .compactMap { notification -> UIBackgroundRefreshStatus? in
                guard let application = notification.object as? UIApplication else {
                    return nil
                }

                return await application.backgroundRefreshStatus
            }
            .eraseToStream()

swift-concurrency-extras version information

1.2.0

Destination operating system

iOS 18.0

Xcode version information

16.0.0

Swift Compiler version information

swift-driver version: 1.115 Apple Swift version 6.0 (swiftlang-6.0.0.9.10 clang-1600.0.26.2)
Target: arm64-apple-macosx15.0

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions