Different returned type when using some vs any in function signature on a view

Hello, given the following code

protocol Service: Sendable {}
struct Impl: Service {}

struct AView: View {
    private let service: any Service

    init(service: any Service) {
        self.service = service
    }

    var body: some View {
        genericView(service: service)
    }

    func genericView(service: some Service) -> some View {
        EmptyView()
    }
}

The body fails to compile with the Type 'any View' cannot conform to 'View' error.
But, if I change the genericView function to take an any Service instead, it successfully compiles.

I've looked at the generated SIL, and the method signature looks like this:

sil hidden [ossa] @$s12SILInspector5AViewV11genericView7serviceQrx_tAA7ServiceRzlF : $@convention(method) <τ_0_0 where τ_0_0 : Service> (@in_guaranteed τ_0_0, @in_guaranteed AView) -> @out @_opaqueReturnTypeOf("$s12SILInspector5AViewV11genericView7serviceQrx_tAA7ServiceRzlF", 0) __<τ_0_0>

whereas when the argument is an existential container, it looks like this :

sil hidden [ossa] @$s12SILInspector5AViewV11genericView7serviceQrAA7Service_p_tF : $@convention(method) (@in_guaranteed any Service, @in_guaranteed AView) -> @out @_opaqueReturnTypeOf("$s12SILInspector5AViewV11genericView7serviceQrAA7Service_p_tF", 0) __

The only difference I mainly see is that the some variant has the generic argument specified at the end (__<τ_0_0>) vs only __ on the existential one.

Can anyone explain what's actually happening?

1 Like

Remember that in parameter position, some is just sugar for a named generic parameter, so what you really have is this:

func genericView<T>(service: T) -> some View

Now, the some in return position is an opaque return type, and those depend on the generic parameters of the declaration they are attached to. To see what this means, let's define a second type that conforms to Service:

struct Impl2: Service {}

Now suppose I call genericView() twice with each one of Impl and Impl2():

let x1 = genericView(service: Impl())
let x2 = genericView(service: Impl())

let y1 = genericView(service: Impl2())
let y2 = genericView(service: Impl2())

Now, x1 and x2 have the same static type, and y1 and y2 have the same static type, but it's not the same as the type of x1 (or x2).

When you call genericView with an any Service, the type that results cannot be expressed:

let anyService: any Service = ...
let z = genericView(service: anyService)
// what is the type of `z`?

A similar situation occurs with plain old generics. Eg, if you have

struct G<T> { var t: T }
func genericView<T>(service: T) -> G<T> { return G(t: service) }

Then genericView(service: Impl()) returns a G<Impl>, andgenericView(service: Impl2()) returns a G<Impl2>.

However, genericView(service: anyService) cannot be expressed because its generic argument would then depend on the given value.

2 Likes