Generics in Swift - "Generic parameter 'T' could not be inferred
IosGenericsSwift3Ios Problem Overview
I'd like to return a UIViewController
conforming to MyProtocol
from a method, so I'm using the method signature:
func myMethod<T where T : UIViewController, T : MyProtocol>() -> T {
First thing I don't understand: if myMethod
returns e.g. a MyViewController
which has to following signature, I have to force cast it:
class MyViewController: UIViewController, MyProtocol
I cannot simply return MyViewController()
but I need to cast it like this: return MyViewController() as! T
- why is this necessary?
And the second thing: how can I use this method somewhere? I cannot simply say
let x = myMethod() as? UIViewController
as I get the error
Generic parameter 'T' could not be inferred
How can I achieve something like this? If I cast it to MyViewController
it works, but I would like to avoid that of course.
EDIT: Example
class MyViewController : UIViewController, MyProtocol {
}
protocol MyProtocol {
}
func myMethod<T>() -> T where T : UIViewController, T : MyProtocol {
return MyViewController() as! T // why is the cast necessary?
}
ok, I do get one part, but why is the cast to T
necessary? MyViewController
is a subclass of UIViewController
and conforms to the protocol, so no cast should be necessary, right?
Ios Solutions
Solution 1 - Ios
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 - Ios
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 - Ios
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
.