Generics in Swift - "Generic parameter 'T' could not be inferred
Solution 1:
func myMethod<T where T : UIViewController, T : MyProtocol>() -> T
This declaration says: There exists a function called myMethod
, such that myMethod
returns some specific T
where T
is a subtype of UIViewController
and also MyProtocol
. This does not say what type T
actually is, and it does not say that there is only one such myMethod
. There can be many if there are many type that are both subclasses of UIViewController
and conform to MyProtocol
. Every one of those types creates a new version of myMethod
(really a new solution to the assertion myMethod
makes, that such a function does exist).
This is not the same thing as:
func myMethod() -> UIViewController
That says: The function myMethod
returns any subtype of UIViewController
.
There is no way in Swift to express "any type that is a subclass of UIViewController and is a subtype of MyProtocol." You can only discuss a specific type that meets that criterial. Swift can't combine classes and protocols this way; it's just a current limitation of the language, not a deep design issue.
The specific versus any is the issue. There are many functions that satisfy your myMethod
declaration. Every T
you can plug in that conforms to the rules would be a candidate. So when you say myMethod()
, the compiler doesn't know which specific T
you mean.
(I was going to expand this answer to provide it in less type-theory, more "how do you do it in code" terms, but donnywals already has an excellent version of that.)
* To your edited question *
func myMethod<T>() -> T where T : UIViewController, T : MyProtocol {
return MyViewController() as! T // why is the cast necessary?
}
T
is a specific type decided by the caller. It is not "any type that conforms" it is "some specific, concrete type that conforms." Consider the case that you called:
let vc: SomeOtherViewController = myMethod()
In this case, T
is SomeOtherViewController
. MyViewController
is not that type, so what you're doing with the as!
cast is dangerous.
Solution 2:
In a method like this, returning T
means you have to return T
. If you return MyViewController
, the return type should be MyViewController
. T
is a generic type that will take the form of whatever the Swift compiler can infer it to be.
So, with your method signature, a simple implementation of the protocol and method could look like this.
protocol MyProtocol {
var name: String { get set }
}
func myMethod<T where T : UIViewController, T : MyProtocol>() -> T {
var vc = T()
vc.name = "Hello, world"
return vc
}
So, considering your usage example:
let x = myMethod()
How would the compiler know what the concrete type of T
is? There is nothing giving it a hint of MyViewController
. The only thing we know is that whatever T
is, it should be MyViewController
or a subclass of it. And it should conform to MyProtocol
. But this does not provide information about what the type of T
should be.
The only place where the compiler can infer what we want T
to be is through the return value. All the code between <>
are constraints for what T
is allowed to be. -> T
is the only place where T
is seen outside of the constraints. So if we can somehow tell the compiler what we want myMethod
to return, we have given it enough information to infer T
.
Your typecast works but I agree that it's not very pretty. A much prettier way for the compiler to infer T
is this.
let vc: MyViewController = myMethod()
By specifying the type of vc
, the compiler understands that we want myMethod
to return a MyViewController
. So now T
's type can be inferred and if we return T
, we actually return MyViewController
.
Solution 3:
As some pointed out in the comments, there is no apparent reason for myMethod
to be generic. The argument for doing so is: (quoting from your comment)
I would like to work with a type that's a UIViewController and conforms to the specific protocol;
Lets call that type ViewControllerAndMyprotocol
,
I do have different classes which conform to this rules, therefore I do not want to use a specific type
However myMethod
signature already constrains the type ViewControllerAndMyprotocol
i.e. caller is bound to receive a UIViewController
and not any of the "different classes which conform to this rules".
The flexibility on what concrete types could be ViewControllerAndMyprotocol
, including MyViewController
is why there is type ambiguity in the statement let x = myMethod()
requiring casting: let x = myMethod() as? UIViewController
You can avoid the casting by changing myMethod
signature as such:
typealias ViewControllerAndMyprotocol = UIViewController & MyProtocol
func myMethod() -> ViewControllerAndMyprotocol {
return MyViewController()
}
The statement let x = myMethod()
will not require casting and will be of type ViewControllerAndMyprotocol
which is also a UIViewController
.