Reputation: 942
I have met a problem like what title has described: the guard statement breaks type inference for some reason. I have created a Playground
project to play around.
Here are some boilerplate code for setup:
import Foundation
struct Model: Decodable {
var i: String
}
let jsonString = """
{
"i": "yes"
}
"""
let jsonData = jsonString.data(using: .utf8)
let theModel = try JSONDecoder().decode(Model.self, from: jsonData!)
struct Response<T> {
var decodedData: T?
}
enum Result<Value> {
case success(Value)
case failure
}
struct Client {
static let shared = Client()
private init() {}
func execute<T: Decodable>(completion: (Response<T>) -> ()) {
let decodedData = try! JSONDecoder().decode(T.self, from: jsonData!)
completion(Response(decodedData: decodedData))
}
}
Here's the problem:
struct ServicesA {
func loadSomething(completion: (Result<Model>) -> ()) {
Client.shared.execute { result in // error: generic parameter 'T' could not be inferred
guard let decodedData = result.decodedData else { return }
completion(Result.success(decodedData))
}
}
}
struct ServicesB {
func loadSomething(completion: (Result<Model>) -> ()) {
Client.shared.execute { result in
completion(Result.success(result.decodedData!))
}
}
}
ServicesA
breaks whereas ServicesB
compiles.
As what you can see, the only difference is guard let decodedData = result.decodedData else { return }
. It breaks the type inference so that the Client.shared.execute
function complains that the T
can't be inferred.
I wonder why would this happen and what's the most appropriate way to deal with this issue.
Upvotes: 2
Views: 197
Reputation: 7552
The compiler only infers closure return types when there is a single statement in the closure.
let x = {
return 1
}()
// x == Int(1)
let y = { // error: unable to infer complex closure return type; add explicit type to disambiguate
print("hi")
return 2
}()
https://forums.swift.org/t/problems-with-closure-type-inference/11859/2
Upvotes: 3
Reputation: 83
TLDR; single line closure compilation is different from multi line
Long answer: Let’s forget single line closures for sometime. When we write generic function accepting a generic as argument type and then call this function, compiler needs to know at time of invocation about what’s the type of function needed i.e. the type of its argument. Now consider this argument is a closure. Again compiler needs to know what’s the type of closure (function signature) at time of invocation. This info should be available in the signature of the closure (i.e. argument and return type), compiler doesn’t worry (and rightly so) about the body of the closure. So Service A is behaving perfectly as expected from compiler i.e. the closure signature doesn’t give any clue of its type.
Now comes in single line closure. Swift compiler has a built in type inference optimisation for single line closures only. When you write single line closure, compiler first kicks in its type inference and try to figure out about the closure including its return type etc from its only single line of body . This type inference kicks in for single line closures (with or without the context of generics). This type inference is the reason that your service B works
So I’ll rephrase the question as “Why does the type inference works for single line closures?” because that’s an extra feature provided by Swift for single line closures only. As soon as you move to multi line closure (its not guard statement specific, same will happen if you put print(“hello world”) as well), this type inference is no more available. Though there may be other type inference checks available for multi line closures.
What you can do is just provide the Type info in closure signature i.e (result: Response< Model >)
Upvotes: 4
Reputation: 6380
Problem is you are referencing data
variable but the compiler doesn't know what type is this (because it is a Generic). Try this:
struct ServicesA {
func loadSomething<T:Model>(completion:(Result<T>) -> ()) {
Client.shared.execute { result:Response<T> in
guard let decodedData = result.decodedData else { return }
completion(Result.success(decodedData))
}
}
}
Upvotes: 0
Reputation: 16446
You need to type cast it result
with Result< Model > so can compiler know about T
struct ServicesA {
func loadSomething(completion:(Result<Model>) -> ()) {
Client.shared.execute { (result:Response<Model>) in
guard let data = result.decodedData else {return}
completion(Result.success(data))
}
}
}
Upvotes: 0