Opaque return types

Overview:

  • I have a doubt regarding opaque return types. Car1View compiles without errors. I was expecting to get the error: Function declares an opaque return type, but the return statements in its body do not have matching underlying types.
  • However Car2View throws the error I was expecting.

Questions:

  1. Is this because in Car1View function builder bundles the if the statement into one view?
  2. Or am I missing something?

Code:

import SwiftUI

struct RedView : View {
    
    var body: some View {
        Color.red
    }
}

struct GreenView : View {
    
    var body: some View {
        Color.green
    }
}

struct Car1View : View {
    
    var price : Int
    
    //No compilation error
    var body: some View {
        
        if price > 100 {
            RedView() 
        } else {
            GreenView()
        }
    }
}

struct Car2View : View {
    
    var price : Int
    
    //Compilation Error: Function declares an opaque return type, but the return statements in its body do not have matching underlying types
    var body: some View {
        
        if price > 100 {
            return RedView()
        } else {
            return GreenView()
        }
    }
}

Car1View actually returns _ConditionalContent<RedView, GreenView>, which describes the entire if-else block. With function builder, the transformed function looks something more like:

var body: some View {
  let c: _ConditionalContent<RedView, GreenView>
  if price > 100 {
    c = ViewBuilder.buildEither(first: RedView())
  } else {
    c = ViewBuilder.buildEither(second: GreenView())
  }
  return c
}

which returns only one type (at the end).

For Car2View, you use explicit return, which disables the function builder. So now you have two return points with different types.

1 Like

Thanks a lot @Lantua for that explanation, so it's almost like a wrapping type, I suppose same is the case with switch statements as well. Well explained, was breaking my head over it. It never occurred to me to print the body type.

let car1Body = Car1View(price: 10).body
print("car1Body = \(type(of: car1Body))")

Output:

car1Body = _ConditionalContent<RedView, GreenView>

Pretty much. They essentially form a binary tree with buildEither (wouldn't expect them to do buildEither(third:) would ya?).

PS

I did omit a few things, like how ViewBuilder should actually also use buildBlock, or how the choice of the binary tree would be ambiguous when there are more than two cases. If you're interested on how the transformation works, you can check SE-0289 which is currently in review, though I believe the precise mechanism is pretty much settled.

1 Like

Thanks a lot @Lantua, this is a pretty cool feature!