Swift Optional Generics Type required even though parameter is nil

Viewed 236

how to fix this method declaration to let Payload be fully omitted when not needed, now it needs Generic Type to be provided even though it should use default nil value. I ended with duplicating this method with Payload part omitted to get rid of this error but looking for any hints so I can deduplicate my code again.

Thanks in advance

func prepareRequest<Payload: Encodable>(for url: URL, method: String = "GET", payload: Payload? = nil) -> AnyPublisher<URLRequest, Never> {
// ...
}
2 Answers

Creating another overload is the way to do it. Swift doesn't have variadic generics yet. Until then, you'll need an overload for each number of placeholders. And zero is one of those numbers!

Typically, this means you'll be using a third (likely private) function for common functionality.

Note: it's impossible to use these default parameters, even though they compile!

private func common() { }

func ƒ() { common() }
ƒ()

func ƒ<T>(_: T? = nil) { common() }
ƒ( () )

func ƒ<T0, T1>(_: (T0, T1)? = nil) { common() }
ƒ(
  ( (), () )
)

Payload has to be something. It doesn't matter in this case what it is, but it still has to be known. The fact that the value is nil doesn't mean that you won't use the type itself in some way inside of the implementation, so Swift needs to know what it is.

The easiest way is to assign it to something already Encodable like String, adding the following overload:

func prepareRequest(for url: URL, method: String = "GET") -> AnyPublisher<URLRequest, Never> {
    return prepareRequest(for: url, method: method, payload: Optional<String>.none)
}

You have to do this with overloads; there's no "default type parameter" feature in Swift. But any time you run into a limitation of default parameters, you can deal with it as an overload. Default parameters are just easier-to-type overloads after all.

If you're like me, having to pick a random type like String is annoying. The correct type here is Never, but Never doesn't conform to Encodable. It should (Never should conform to everything), but we can fix that by hand:

extension Never: Encodable {
    public func encode(to encoder: Encoder) throws {
        fatalError()
    }
}
func prepareRequest(for url: URL, method: String = "GET") -> AnyPublisher<URLRequest, Never> {
    return prepareRequest(for: url, method: method, payload: Optional<Never>.none)
}

There's no really tangible advantage doing it this way than using String, but it makes me feel better.

Related