swiftunion-typesproperty-wrapper

Swift: Union Type in Property Wrapper


In Swift, you can specify a union type using an enum. For example:

enum IntOrString {
    case int(Int)
    case string(String)
}

Now I want to write a property wrapper, which I can apply to properties of a type from my union type. However, I don't want to expose the enum I use behind the scenes. Instead, a user of my property wrapper should be able to apply it to a String or Int property directly.

One approach would work as follows.

@propertyWrapper
struct Managed<T> {
    private var storage: IntOrString
    var wrappedValue: T {
        get {
            switch storage {
                case .int(let int):
                    return int as! T
                case .string(let string):
                    return string as! T
            }
        }
        set {
            if T.self == Int.self {
                storage = .int(newValue as! Int)
            } else if T.self == String.self {
                storage = .string(newValue as! String)
            } else {
                fatalError("Property has an illegal type")
            }
        }
    }
}

Unfortunately however, I lose static type safety with this approach, as a user can now apply Managed to a property which is neither Int nor String and would only get a runtime error, once he tries to set the property.

struct Foo {
    @Managed var bar: Double
}

let foo = Foo(bar: 0) // fatal error

As far as I'm aware, I cannot specify a type constraint on T which only allows for T to be Int or String. So is there any way in Swift to get static type safety on union types in this scenario? If not, are there more elegant workarounds to my approach?


Solution

  • As far as I'm aware, I cannot specify a type constraint on T which only allows for T to be Int or String.

    Actually you can with a helper protocol

    protocol OnlyStringOrInt {}
    extension Int: OnlyStringOrInt {}
    extension String: OnlyStringOrInt {}
    

    With this solution the property wrapper can be reduced to

    @propertyWrapper
    struct Managed<T: OnlyStringOrInt> {
        var wrappedValue: T
    }
    

    Then bar declared as Double raises this compiler error

    Generic struct 'Managed' requires that 'Double' conform to 'OnlyStringOrInt'