Ben Krueger
Ben Krueger

Reputation: 1546

Cannot explicitly specialize a generic function, multiple generics

There are a lot of answers surrounding my issue, but the solutions I have tried for them have not quite solved the problem. I'm not sure if it has something to do with having multiple generics or something else (also newer to Swift so still wrapping my head around syntax).

I noticed a lot of commonality in my API request code so I decided to abstract the main work out and have it work with any codable request/response objects. Here is my main request method:

private func sendRequest<T: Encodable, U: Decodable>(url: URL, requestModel: T) -> Promise<U> 

And I am trying to call it as such:

public func signIn(requestModel: SignInRequest) -> Promise<SignInResponse> {
    let url = URL(string: authURL + "/signin")!

    //getting compile error "Cannot explicitly specialize generic function" on this line
    return sendRequest<SignInRequest, SignInResponse>(url: url, requestModel: requestModel)
}

I've tried assigning directly to the return object:

let x : Promise<SignInResponse> = sendRequest(...)
return x

to help the compiler out (as suggested in other solutions) but still same issue. Any insights?

Upvotes: 0

Views: 664

Answers (2)

Alvae
Alvae

Reputation: 1294

Inference will succeed if you give enough context with the function's arguments to determine all generic parameters.

From you code, I suspect this is not the case as neither does the generic type U neither appear as an argument, nor there is an additional type constraint to specify it. Therefore, the compiler can't infer what is the concrete type of your function's return value from a function call.

Hence, if there's a relationship between T and U, you may use it as an additional constraint to "help" the compiler infer U once it found T. Here is a minimal example:

protocol Foo {
  associatedtype Bar
  var bar: Bar { get }
}

struct FooImpl: Foo {
  let bar: Int = 0
}

func f<T, U>(foo: T) -> U where T: Foo, U == T.Bar  {
  return foo.bar
}

let a = f(foo: FooImpl())

Protocol Foo has an associated type Bar. By using this association in the function f's signature to add a constraint on its return type, Swift's compiler is now able to fully infer the specialized signature of f when I call it with an instance of FooImpl.


In your particular example, I would suggest writing a protocol to which the requestModel parameter should confirm, with an associated type to place a constraint on the return value. For instance:

protocol RequestModel: Encodable {
  associatedtype ResponseModel: Decodable
}

struct AnAwesomeModel: RequestModel {
  typealias ResponseModel = String
}

private func sendRequest<T: RequestModel, U>(url: URL, requestModel: T) -> Promise<U>
  where U == T.ResponseModel
{
  // ...
}

// x's type will be inferred as String
let x = sendRequest(url: URL("http://...")!, requestModel: AnAwesomeModel())

Upvotes: 0

Ben Krueger
Ben Krueger

Reputation: 1546

I ended up using a solution based on this post: https://stackoverflow.com/a/36232002/1088099

I explicitly passed in the request and response object types as parameters:

private func sendRequest<T: Encodable, U: Decodable>(requestModelType: T.Type, responseModelType: U.Type, url: URL, requestModel: T) -> Promise<U> 

and calling:

public func signIn(requestModel: SignInRequest) -> Promise<SignInResponse> {
    let url = URL(string: authURL + "/signin")!

    return sendRequest(requestModelType:SignInRequest.self,
                       responseModelType:SignInResponse.self,
                       url: url,
                       requestModel: requestModel)
}

Not as clean of a solution I was hoping for, but works.

Upvotes: 0

Related Questions