Generic struct initialization with unknown type

Hello,


I'd like to have a generic struct that can be initialized with some data whose type is constrained but unknown up to the point of initialization. The motivation for this is that I have certain "value types" which can or cannot be created from raw data (i.e. AnyObject). The aforementioned struct should be initializable with raw data from which such a value type can be created. Here's the code:

protocol ValueType {
    init?(raw: AnyObject)
}

struct StringValue: ValueType {
    var value: String
  
    init?(raw: AnyObject) {
        guard let stringValue = raw as? String else { return nil }
        self.value = stringValue
    }
}

struct IntValue: ValueType {
    var value: Int
  
    init?(raw: AnyObject) {
        guard let intValue = raw as? Int else { return nil }
        self.value = intValue
    }
}

struct ValueContainer<T: ValueType> {
    var value: T
  
    init(value: T) {
        self.value = value
    }
  
    init?(raw: AnyObject) {
        guard let value = T(raw: raw) else { return nil }
        self.init(value: value)
    }
}


With that I can create a ValueContainer instance only if I explicitly specify the type in angle brackets:

let intValueContainer = ValueContainer<IntValue>(raw: 1) // <-- works


Ideally I'd like to let the initializer figure out the type itself so I don't have to know it in advance (checking all possible types manually inside the initizlier would be ok for me), but I don't know if that's possible at all – at least I did not find a working solution. If this turns out to be impossible, e.g. because T must be known before init can be called, is there a way to make a factory method (which may live outside of ValueContainer) that implements the described behavior? I failed to find a solution for that, too.

Answered by Jens in 76597022

I think the reason for why you are not able to find a solution that you are satisfied with is that you want to use generics for this even though

generics is a compile time thing, designed to be used with static types, and mixing static generics with dynamic types at runtime is rather like mixing steak and ice cream. Steak is great. Ice cream is great. Steak with ice cream is a bit unpleasant.


The above is from a talk called Zero Cost Abstractions by Airspeed Velocity (at ~ 1:40).

(I won't include the link because then this post would probably get stuck waiting for moderation for a very long time, but the talk is easy to find.)


I don't know enough about your concrete goal here, but I guess you could try to use an enum (but you probably already have), or just accept that what's knowable only at runtime can't be represented in static types / be known at compile time, thus: Model the parts that has to be dynamic/runtime just like you would in Objective-C, ie by simply using the dynamic features of the language (reference types, dynamic typing, dynamic typechecking, dynamic dispatch, runtime polymorphism, etc.).


The difference between ObjC and Swift in this regard is just that Swift adds a lot of static/compile time features, which is very nice, but it doesn't mean that you can take advantage of the static/compile time features for dynamic/runtime things. And also there's no need to recreate any (still existing) dynamic features by jumping through hoops.

After re-reading my post I think I should add that the sample code only really outlines the general kind of problem. In reality, my "ValueType" subtypes are far more complex and in no way related to Int or String. The Container type actually represents arrays of ValueTypes rather than a single instance, and (among other things) I need the container to make sure that the array stores arbitrary but fixed ValueType types.

Okay I think I found a solution for the factory method approach. It took a number of hacks to convince the compiler to finally compile, however, and it therefore doesn't feel like a solid approach. Let me discuss the details:


The first part mostly remains the same as above.


// ValueTypes
protocol ValueType {
    init?(raw: AnyObject)
}

struct StringValue: ValueType {
    var value: String

    init?(raw: AnyObject) {
        guard let stringValue = raw as? String else { return nil }
        self.value = stringValue
    }
}

struct IntValue: ValueType {
    var value: Int

    init?(raw: AnyObject) {
        guard let intValue = raw as? Int else { return nil }
        self.value = intValue
    }
}

// ValueContainer
struct ValueContainer<T: ValueType> {
    private var value_: T

    init(_ value: T) {
        value_ = value
    }
}


But then it's getting ugly:


// (1) A wrapper protocol that hides the T from ValueType<T>
protocol ValueContainerType {
    var value: ValueType { get } // (2) A getter for the contained value
}

extension ValueContainer: ValueContainerType {
    var value: ValueType { return value_ } // (3) Using "value" directly doesn't work, so we need to pretend to cast it
}

// ValueContainer Factory
func ValueContainerFromRaw(raw: AnyObject) -> ValueContainerType? { // (4) The method cannot return ValueContainer
    if let intValue = IntValue(raw: raw) {
        return ValueContainer(intValue)
    } else if let stringValue = StringValue(raw: raw) {
        return ValueContainer(stringValue)
    } else {
        return nil
    }
}


A factory method cannot return ValueContainer directly, since "Reference to generic type 'ValueContainer' requires arguments in <...>" (4). Okay, so I wrap ValueContainer inside a protocol ValueContainerType (1), which successfully hides the generic nature of ValueContainer from the compiler. The downside is that instances returned by the factory method lose ValueContainer's type definition, i.e. its methods and variables cannot be accessed any longer. As a workaround, I duplicate its interface and make it part of the protocol (2). However, accessing "value" still won't work, even though it is guaranteed that ValueContainer's T type conforms to ValueType. So as a further workaround I cast the internal value to a ValueType using a computed property (3).


Now it works, but I feel like I'm fighting the system. Any comments on why this is and if this might change, or if there is a cleaner way, would be very much appreciated.

> I need the container to make sure that the array stores arbitrary but fixed ValueType types.


I think it's not clear what you mean by "arbitrary but fixed".


Fixed:

Do you mean that the elements of a particular Container-instance are all of the same type, so a Container is like a homogeneous collection type?


Arbitrary:

Do you mean that your Containers' "ElementType" can't be known at compile time?

Yes and yes. The "raw data" that is fed into the factory method is valid iff it is an array that contains data elements of a certain format. There are several possible formats (the different ValueTypes), but all elements of the array must have the same format. To remain with the (extremely oversimplified) example: the input is AnyObject, but instances of the container can only be constructed if this data is actually [IntValue] or [StringValue], but not a mixed type array.


The type checking of the raw data (which comes from NSJSONSerialization) is quite advanced and cannot be performed beforehand.

Accepted Answer

I think the reason for why you are not able to find a solution that you are satisfied with is that you want to use generics for this even though

generics is a compile time thing, designed to be used with static types, and mixing static generics with dynamic types at runtime is rather like mixing steak and ice cream. Steak is great. Ice cream is great. Steak with ice cream is a bit unpleasant.


The above is from a talk called Zero Cost Abstractions by Airspeed Velocity (at ~ 1:40).

(I won't include the link because then this post would probably get stuck waiting for moderation for a very long time, but the talk is easy to find.)


I don't know enough about your concrete goal here, but I guess you could try to use an enum (but you probably already have), or just accept that what's knowable only at runtime can't be represented in static types / be known at compile time, thus: Model the parts that has to be dynamic/runtime just like you would in Objective-C, ie by simply using the dynamic features of the language (reference types, dynamic typing, dynamic typechecking, dynamic dispatch, runtime polymorphism, etc.).


The difference between ObjC and Swift in this regard is just that Swift adds a lot of static/compile time features, which is very nice, but it doesn't mean that you can take advantage of the static/compile time features for dynamic/runtime things. And also there's no need to recreate any (still existing) dynamic features by jumping through hoops.

Thank you, Jens! Although I'm not new to Swift, I'm kind of just starting to realize how the consequences of Swift's type safety impact my software architectures in sometimes unexpected ways. Actually I sort of avoided generics up to now for that very reason, but understanding that it's a strict compile-time feature helps me embrace this technique fearlessly 🙂


While I'm at it, allow me to ask a related question. There are two ways to write a single-parameter function:

func a(param: SomeProtocol) {}

and

func a<T: SomeProtocol>(param: T) {}


What's the specific use case for the two variants? And are there any particular reasons to favor one over the other besides compiler optimization considerations?

Some differences I can think of right now (there are probably more):


The former will perform type erasure and thus use dynamic dispatch (on arguments of any concrete type conforming to SomeProtocol, which means performance overhead and lost type information).


As it can't know the concrete type of its parameter, there's no way for a function like the former to for example return a value of the same type as the parameter. The latter can of course use its type parameter T for that.


Also, the former can't be used if SomeProtocol has Self or associated type requirements.


The latter can't be used if the concrete type of the argument is not knowable at compile time, ie if the argument's type is SomeProtocol (rather than eg SomeConcreteType conforming to SomeProtocol).

Generic struct initialization with unknown type
 
 
Q