Lighter Fare

Publisher<T, Never> is great, but...

In Combine, Publishers with Failure == Never are great. There are a lot of benefits to having the type system keeping track of whether our publishers can fail or not. But what if you have a Publisher<T, Never> and want to merge it with a Publisher<T, MyError>? In order to use Combine's merge(with:) operator we need to the two publishers to have the same Failure type.

Given that a Publisher<T, Never> can never fail, it should be fairly straightforward to convert it to a Publisher<T, MyError> as we only have to change the type; we don't actually need to convert any errors. My first idea was to use the mapError operator:

extension Publisher where Failure == Never {
    func promoteError<E>(_ errorType: E.Type = E.self) -> AnyPublisher<Output, E> {
        self
            .mapError { (_: Never) -> E in fatalError() }
            .eraseToAnyPublisher()
    }
}

This works, but the compiler complains with a warning on the mapError closure:

Will never be executed
'_' is uninhabited, so this function body can never be executed

The compiler is not wrong. The closure will never be executed because the publisher will never fail. That's the whole point! But Swift doesn't have any way to silence warnings, so this is a bit problematic.

The only solution I could think of was a full-blown custom Publisher implementation:

extension Publishers {
    struct PromoteError<Upstream: Publisher, NewError: Error>: Publisher where Upstream.Failure == Never {
        typealias Output = Upstream.Output
        typealias Failure = NewError

        let upstream: Upstream

        init(upstream: Upstream) {
            self.upstream = upstream
        }

        func receive<S>(subscriber: S) where S : Subscriber, Failure == S.Failure, Output == S.Input {
            // My publisher doesn’t have to do anything but wrap the downstream
            // subscriber in my custom subscriber and pass it along to the upstream.
            let newSubscriber = Subscribers.PromoteError(downstream: subscriber)
            upstream.receive(subscriber: newSubscriber)
        }
    }
}

extension Subscribers {
    class PromoteError<Downstream: Subscriber>: Subscriber {
        typealias Input = Downstream.Input
        typealias Failure = Never

        let downstream: Downstream

        init(downstream: Downstream) {
            self.downstream = downstream
        }

        func receive(subscription: Subscription) {
            // Pass along the subscription without modification
            downstream.receive(subscription: subscription)
        }

        func receive(_ input: Downstream.Input) -> Subscribers.Demand {
            // Pass along all input without modification
            downstream.receive(input)
        }

        func receive(completion: Subscribers.Completion<Never>) {
            // Here’s where the magic happens! Because the `Failure` type 
            // is `Never`, the compiler knows that `Completion.failure`
            // is an impossible case and allows me to omit it entirely
            // from this switch. The downstream subscriber is capable of
            // receiving an error, but we’ll never send one.
            switch completion {
            case .finished:
                downstream.receive(completion: .finished)
            }
        }
    }
}

With that in place, I can define the operator without any warnings:

extension Publisher where Failure == Never {
    func promoteError<E>(_ errorType: E.Type = E.self) -> AnyPublisher<Output, E> {
        Publishers.PromoteError(upstream: self).eraseToAnyPublisher()
    }
}

This feels like a lot of work to do a simple thing, so I think I may be missing something obvious. Let me know if you know how to silence that warning or know of a simpler way to convert the Failure type from Never to any other Error type.

Update

Thanks to some helpful folks on Twitter, I got an answer to both my questions. Firstly, Combine already has this operator: Publisher.setFailureType(to:). Very Helpful!

Secondly, you can write a function or closure that takes Never as an argument without the compiler complaining just by leaving the body empty. So I could have implemented the operator like this:

func promoteError<E>(_ errorType: E.Type = E.self) -> AnyPublisher<Output, E> {
    self
        .mapError { (_: Never) -> E in } // No warning!
        .eraseToAnyPublisher()
}

Turns out the compiler just didn't like the presence of the fatalError statement which it knew could never be executed.

Tagged with: