Store type in variable then use in generic function

When storing the type in a variable and then using it as an argument in a generic function the compiler says it cannot convert it.

protocol Baz {
    var x: String { get }
}

final class Foo: Baz {
    var a: String
    var b: String
    var x: String
    init() {
        a = "a"
        b = "b"
        x = "x"
    }
}

func stuff<A>(_ a: A.Type) where A: Baz {
    print(A.self)
    print(a.self)
}

let a: Baz.Type = Foo.self
// Generic parameter 'A' could not be inferred
// Cannot convert value of type 'Baz.Type' to expected argument type 'A.Type'
stuff(a)

// Works
stuff(Foo.self)

It's not clear to me why this is not allowed. Can someone help me understand?

You've erased the concrete type to the protocol type with Baz.Type = Foo.self and protocols don't conform to themselves, so you can no longer pass the value through the function which requires Baz conformance.

1 Like

That makes sense. In order for me to be able to store that type and still be able to send it to stuff I need to put Foo in a Box.

struct Box<A: Baz> {
    let type: A.Type

    init(putInBox type: A.Type) {
        self.type = type
    }
}

let a = Box(putInBox: Foo.self)
stuff(a.type)

Tada!

The only problem with that is that the Box type is generic and "leaks" the type information. Are there other design patterns that allow me to "Box" it up and still pass it to stuff? Edit: I feel like an opaque return types might be the answer... but I'm not sure.

Box serves no purpose. It doesn't perform any kind of type erasure. You might as well just store Foo in a variable directly:

let foo: Foo.Type = Foo.self
stuff(foo)
1 Like

Fair enough. But I think I was already aware of that...

The only problem with that is that the Box type is generic and "leaks" the type information.

So what is the way to erase it properly?

There are many approaches, all of which erase the original type to some extent. Some approaches capture implementation, perhaps through closures or values, some abstract the internals through a protocol, sort of like what you're already trying to do. There are many articles about it, here's one: Different flavors of type erasure in Swift | Swift by Sundell. For your Baz, with its one requirement, it's fairly easy:

struct AnyBaz: Baz {
  var x: String

  init<T: Baz>(_ aBaz: T) {
    x = aBaz.x
  }
}

For more complex protocols there are additional approaches.

One other approach I forgot to mention is, of course, using the protocol as the box itself. Just storing a collection of Baz values should work until you start using a protocol requirement, like Self or associatedtypes, that prevent it.

Terms of Service

Privacy Policy

Cookie Policy