dmitri
1
I have the following problem:
protocol Letter {
init()
}
struct A: Letter {}
struct LetterContainer<L: Letter> {
var letter: L
}
func initializeLetterContainer<L: Letter>(_ letter: L){
let result = LetterContainer<L>(letter: letter)
print(result)
}
// add a return type that includes L and it fails
func initializeAndReturnLetterContainer<L: Letter>(_ letter: L) -> LetterContainer<L> {
let result = LetterContainer<L>(letter: letter)
print(result)
return result
}
func getLetterType() -> Letter.Type {
return A.self
}
let letterType = getLetterType()
let letter = letterType.init()
initializeLetterContainer(letter) // works
// _ = initializeAndReturnLetterContainer(letter) // Fails to compile: Type 'any Letter' cannot conform to 'Letter'
why does the generic function initializeLetterContainer consume the returned result of getLetterType() just fine while a similar function, initializeAndReturnLetterContainer fails to compile?
2 Likes
ole
(Ole Begemann)
2
I think you're running into this limitation of SE-0352: Implicitly Opened Existentials:
When T or a T-rooted associated type appears in a non-covariant position in the result type, T cannot be bound to the underlying type of an existential value because there would be no way to represent the type-erased result. This is essentially the same property as descibed for the parameter types that prevents opening of existentials, as described above. For example:
func cannotOpen7<T: P>(_ value: T) -> X<T> { /*...*/ }
In your example, L appears in non-covariant position in the function's return type (generic parameters in Swift aren't covariant, with a few hardcoded exceptions such as Array), so this limitation applies.
3 Likes
jjrscott
(John Scott)
4
1 Like
dmitri
5
I tried doing that but it fails to compile (I think for the same reason as the original version of code):
protocol Letter {
init()
}
struct A: Letter {}
struct LetterContainer<L: Letter> {
var letter: L
}
func initializeLetterContainer<L: Letter>(_ letter: L){
let result = LetterContainer<L>(letter: letter)
print(result)
}
// add a return type that includes L and it fails
func initializeAndReturnLetterContainer<L: Letter>(_ letter: L) -> LetterContainer<L> {
let result = LetterContainer<L>(letter: letter)
print(result)
return result
}
func getLetterType() -> Letter.Type {
return A.self
}
extension Letter {
func initializeAndReturnLetterContainerFromSelf() -> LetterContainer<Self> {
return initializeAndReturnLetterContainer(self)
}
}
let letterType = getLetterType()
let letter = letterType.init()
initializeLetterContainer(letter) // works
// _ = initializeAndReturnLetterContainer(letter) // Fails to compile: Type 'any Letter' cannot conform to 'Letter'
// _ = letter.initializeAndReturnLetterContainerFromSelf() // Fails to compile: Member 'initializeAndReturnLetterContainerFromSelf' cannot be used on value of type 'any Letter'; consider using a generic constraint instead
jjrscott
(John Scott)
6
It works if you remove the generic from LetterContainer:
struct LetterContainer {
var letter: Letter
}
Is that acceptable given you're not using it due to getLetterType()?
jjrscott
(John Scott)
7
I guess I'm wondering what you would expect ???? to be here given the concrete type is hidden inside getLetterType():
let letterContainer: LetterContainer<????> =
initializeAndReturnLetterContainer(getLetterType().init())
dmitri
8
Yes, that would be an acceptable work-around.
1 Like
dmitri
9
One would expect a container with something that conforms to the Letter protocol.
I have a similar issue, although with a bit more generics unfortunately 
Here is my situation:
public protocol TabGeneric {
associatedtype Content: View
var name: String { get }
var image: Image? { get }
@ViewBuilder var content: () -> Content { get }
}
public final class TabBuilder {
public init() {}
func tabs(_ tabs: [any TabGeneric]) {
let t = tab(tabs[0])
}
// func tab(_ tab: some TabGeneric) -> Text {
func tab<T: TabGeneric>(_ tab: T) -> TabComponentView<T> {
TabComponentView(content: tab.content)
// Text("\(tab.name)")
}
}
public struct TabComponentView<T: TabGeneric>: View {
let tab: T
public var body: some View {
tab.content()
.tabItem {
tab.image
Text(tab.name)
}
.tag(tab.name)
}
}
The code fails in the tabs function, on the line: let t = tab(tabs[0])
I can't wrap my head around why it fails. I understand that this cannot work:
func cannotOpen7<T: P>(_ value: T) -> X<T> { /*...*/ }
because of what @ole said:
generic parameters in Swift aren't covariant, with a few hardcoded exceptions such as Array
But is there a way around this somehow?
My final goal is to take [any TabGeneric] and create a TabView out of it, ie:
func tabView(_ tabs: [any TabGeneric]) -> some View {
TabView {
ForEach(tabs.indices, id: \.self) { tabIndex in
tab(tabs[tabIndex])
}
}
}