Reputation: 473
I am trying to create a method that will return a specialized instance of some generic type. Let's assume the following example:
class Base { }
class Foo: Base {}
class Bar: Base {}
class MyGenericView<T> where T: Base {
func show(with item: T) { }
}
class MySpecializedViewFoo: MyGenericView<Foo> { }
class MySpecializedViewBar: MyGenericView<Bar> { }
With the above given, I would like to have a function like:
func createView<T: Base>(for item: T) -> MyGenericView<T>
but when I try to implement it like
func createView<T: Base>(for item: T) -> MyGenericView<T> {
if item is Foo {
return MySpecializedViewFoo()
} else if item is Bar {
return MySpecializedViewBar()
}
fatalError("Unsupported item")
}
Then I receive
"Cannot convert return expression of type 'MyGenericView' to return type 'MyGenericView'" error.
Is that something that can be achieved? Could somebody please take a look and point an error in my understanding of that?
Upvotes: 0
Views: 44
Reputation: 271355
You can forcibly make the compiler trust you by doing:
func createView<T: Base>(for item: T) -> MyGenericView<T> {
if item is Foo {
return MySpecializedViewFoo() as! MyGenericView<T>
} else if item is Bar {
return MySpecializedViewBar() as! MyGenericView<T>
}
fatalError("Unsupported item")
}
The compiler doesn't let you do this because it is smart enough to do control flow analysis to determine the bounds of generic type parameters at any given point of a method. As far as it is concerned, return MySpecializedViewFoo()
means the same thing inside and outside the if statement.
The compiler isn't designed like that because you are not supposed to checking the type of a generic type parameter anyway.
You should use two overloads instead:
func createView(for item: Foo) -> MyGenericView<Foo>
func createView(for item: Bar) -> MyGenericView<Bar>
In fact, your checks aren't adequate at all in the first place, so even if the compiler is smart enough, it will tell you that what you are doing isn't safe. Here's an example of how this could go wrong:
class FooSubclass: Foo {}
let foo = FooSubclass1()
let view: MyGenericView<FooSubclass1> = createView(foo)
Using the fix at the beginning, this will crash at runtime. createView
will try to create and return a MySpecializedViewFoo
, which is not a MyGenericView<FooSubclass1>
. Swift generics are invariant. Note that in this case, it will go into the is Foo
branch, not the fatalError
branch.
Another case is using Base
as T
:
let foo: Base = Foo()
let view: MyGenericView<Base> = createView(for: foo)
This again will not go into the fatalError
branch. If you make Base
a protocol, you can make it produce an error when T
is inferred to be Base
. If you also make Foo
and Bar
final
, then I think that should be safe, from the point of view of the hypothetical "smart" compiler.
Upvotes: 1