SwiftUI "ForEach" with inconsistent Data type and Closure Type, no compiler error generated? How does inference of type works in this case?

159 views Asked by At

Following code could be compiled and run without warning or message, but how can it work?

struct ContentView: View {
    var lists = ["a", "b"]
    var body: some View {
        VStack {
            ForEach(lists, id: \.self) { (str: String?) in 
                if let str {
                    Text(str)
                }
            }
        }
    }
}

It seems that the lists conforms to RandomAccessCollection<Element> hence Element is inferred as String, but how can I pass a closure of type (String?) -> some View to ForEach? Shouldn't it only allow the closure of type (String) -> Content ? Also in the description of RandomAccessCollection, why there are two Element type? One is passed as generic argument, and another one is an associated type. Are they considered as one type or two different types?

Comments:

The problem has been solved, this seems to be some sort of automatic sub-typing. Indeed, a more general argument will be true in swift. T1->T2 is a subtype of T3->T4 if and only if T3 is a subtype of T1 and T2 is a subtype of T4 (T is a subtype of T of course). Swift recognise T as a subtype of T?. It is intuitive but should be mentioned in the document.

For example, following code will be compiled without errors:

struct Solution {
    lazy var fun1: (Int) -> Any = fun2
    var fun2: (Any) -> Int
}
1

There are 1 answers

1
Joakim Danielson On

This isn't about ForEach or RandomAccessCollection but about closures, you can apply the same definition of the closure parameter if you use forEach or compactMap etc on the lists array.

 ["a", "b"].forEach { (str: String?) in 
     if let str { print(str) }
 }

The closure is a function and when you define the parameter of this function to be String? then the compiler has no issue with passing String objects to the closure. The opposite would of course not work, defining the parameter as String and then trying to pass an optional value.