Is there any way to specify a generic type which is constrained to either a type or an optional type?

So let's say I want to implement a property wrapper which operates on either Bool or Bool?. For instance, something like this:

@propertyWrapper
struct BoolWrapper<Value: Some constraint here?> {
    var wrappedValue: Value
    ...
}

So that it can be used in either of these ways:

@BoolWrapper var foo: Bool = true
@BoolWrapper var bar: Bool? = nil

But not with any type which is not Bool or Bool?

Is there any way to express this?

Yes.

Start from the beginning: the only expressible constraints for generic types in Swift are protocol conformances, subclassing, and combination of those:

struct MyGenericType<T: SomeProtocol> { ... }
struct MyGenericType<T: SomeClass> { ... }
struct MyGenericType<T: SomeClass & SomeProtocol> { ... }

Bool and Bool? are not related in any way by subclassing. The only remaining solution is thus protocol conformance.

So let's define a protocol that only Bool and Bool? conform to!

protocol Boolish { /* any useful common methods here */ }
extension Bool: Boolish { }
extension Optional: Boolish where Wrapped == Bool { }

@propertyWrapper
struct BoolWrapper<Value: Boolish> {
    var wrappedValue: Value
}

struct Container {
    @BoolWrapper var foo: Bool = true
    @BoolWrapper var bar: Bool? = nil
    
    // Error: Referencing initializer 'init(wrappedValue:)' on 'BoolWrapper'
    // requires that 'String' conform to 'Boolish'
    // @BoolWrapper var baz: String = ""
}

Swift won't let you restrict Boolish conformance on Bool and Bool? only, though. You may have to explicitly document that no other types should adopt this protocol.

1 Like

For now. My pitch for generalized supertype constraints was well received. We just can't move it forward until somebody volunteers to work on implementation. If / when that happens the compiler would recognize the Bool: Bool? relationship in a generic constraint.

Also for now. Hopefully we can express closed protocols in the language eventually. This has been the topic of several threads in the past.

1 Like

Yes. The question was framed against the current state of the language, so I did not think it was useful to raise the complexity of the answer, and transform curiosity into mourning of unborn futures. Today I believe my answer is correct, complete, and actionable.

Absolutely. I just wanted to add the context that there may be other options in the future.