Erasing (or mapping) value type parameters

i have a struct, let’s call it DriverOptions<T> that has a type parameter, and lots and lots of stored properties:

@frozen public
struct DriverOptions<Authentication>
    where Authentication:AuthenticationMode
{
    public
    var authentication:Authentication?

    public
    var connectionTimeout:Milliseconds
    public
    var replicaSet:String?
    public
    var tls:Bool?
    ...

the type parameter is needed when building the DriverOptions instance itself, but now i want to store it inside another type, let’s call it DriverBootstrap, that is not generic, because the DriverBootstrap does not need to know what AuthenticationMode it has in its options.

something like:

public
struct DriverBootstrap
{
    let options:DriverOptions<any AuthenticationMode>

or perhaps

public
struct DriverBootstrap
{
    let options:DriverOptions<Never>

but neither of these ideas are practical to implement, because we cannot upcast from DriverOptions<T> to DriverOptions<any AuthenticationMode>, and it’s a huge pain to write out the very long memberwise init to implement something like map<T>(_:) for this type.

i also thought about defining a protocol DriverOptionsWithAnyAuthenticationMode, but this has a similar problem with the giant memberwise init in that the protocol has to restate every non-generic stored property in the DriverOptions struct.

1 Like

You're hitting one of those seemingly-new-because-of-the-unidirectional-flow-of-genericish-any "protocols that only apply to one type" cases.

I.e. it's not a problem if you copy everything over, like you say.

protocol DriverOptions {
  associatedtype Authentication: AuthenticationMode
}

      public
    struct
  DriverBootstrap
{
  let options: any DriverOptions
    }

I still can't find that other person who was asking for "some/any ConcreteType" functionality. They'll be comin' out the woodwork soon though.

Not particularly clean because of the "as!" cast but does the trick:

public protocol DriverOptionsProtocol {}

@frozen public struct DriverOptions<Authentication: AuthenticationMode> : DriverOptionsProtocol {
    public var authentication: Authentication?
    public var connectionTimeout: Milliseconds = 1
    public var replicaSet: String?
    public var tls: Bool?
}

public struct DriverBootstrap {
    var options: DriverOptionsProtocol
}

struct MyAuth: AuthenticationMode {
    var field: Int = 0
}

let s = DriverBootstrap(options: DriverOptions<MyAuth>.init(authentication: MyAuth(), connectionTimeout: 1))
var options = s.options as! DriverOptions<MyAuth>
options.authentication?.field = 123

Thinking more on this, we currently have the option, externally, to enforce opacity. But not internally. Where does the problem come in with that, for your use case?

struct DriverBootstrap<AuthenticationMode: Module.AuthenticationMode> {
  let options: DriverOptions<AuthenticationMode>
}

enum 🐾: AuthenticationMode { }

let driverOptions = DriverOptions<🐾>()
let someDriverOptions: DriverOptions<some AuthenticationMode> = driverOptions
let bootstrap = DriverBootstrap(options: driverOptions) // DriverBootstrap<🐾>
let someBootstrap = DriverBootstrap(options: someDriverOptions) // DriverBootstrap<some AuthenticationMode>

it can be very annoying to parrot type parameters everywhere a commonly-used type, like DriverBootstrap, travels, and AuthenticationMode shouldn’t be more than an implementation detail at the level of abstraction of DriverBootstrap.

I'm not fully understanding the issue. In the ideal case what should be the type of the property stored in DriverBootstrap. You wrote DriverOptions<any AuthenticationMode> but this is not allowed because any AuthenticationMode doesn't conform to AuthenticationMode.

Also, what should DriverBootstrap be able to do with that property?

DriverBootstrap doesn’t do anything with that property, the information from the generic property is extracted in the process of creating a DriverBootstrap from a DriverOptions<T>. DriverBootstrap needs access to the other myriad of non-generic stored properties that DriverOptions<T> has.

right now, i am just copying each stored property from DriverOptions<T> manually into the definition of DriverBootstrap, with an egregiously long memberwise init.