I'm writing a little library for managing dependencies and I'm running into a weird language bit that I think should work but gives me extremely unhelpful errors.
I defined a protocol that mirrors UserDefaults and I want to integrate it with how I store other dependencies (like keychain and files).
protocol DefaultsStorage {
func value(forKey key: String) -> Any?
func setValue(_ newValue: Any?, forKey key: String)
}
protocol DependencyKey {
associatedtype Value
associatedtype Storage
func value(in storage: Storage) -> Value?
func setValue(_ newValue: Value?, in storage: Storage)
}
/// This is a simplification of how I aim to use the protocols.
@propertyWrapper struct Dependency<Value, Storage> : DynamicProperty {
@State private var cache = DependencyCache<Value>()
let key: any DependencyKey<Value, Storage>
let storage: Storage
var wrappedValue: Value? {
get {
/// over simplified for the example
/// this would actually happen once in update()
cache.value ?? key.value(in storage: storage)
}
nonmutating set {
cache.update(newValue)
key.setValue(newValue, in: storage)
}
}
}
However when I write a simple value to implement DependencyKey
, I'm locked into choosing a concrete type for Storage.
struct SystemDefaultsKey<Value> : DependencyKey {
typealias Storage = // Something must go here?
let name: String
func value(in storage: Storage) -> Value? {
storage.value(forKey: name) as? Value
}
///...
}
I've tried creating a refinement of DependencyKey that specifies a specific type of storage
protocol DefaultsDependencyKey: DependencyKey where Storage: DefaultsStorage {}
However, In my concrete types I still have to specify a concrete Storage type (UserDefaults
for simplicity). Using any DefaultsStorage
doesn't work either because it can't conform to DefaultsStorage
.
When using this protocol in a property wrapper, I use @Environment
to access the storage property to pass to the key. However, all my keys become useless because the storage type is now Environment<UserDefaults>
. I want to instead confine Storage to a specific protocol.
Protocol constraints seems like it should be the solution but the compiler warnings are not helpful. I tried to express it using typical generic clause and using the new some
keyword (combined here in one example).
protocol DependencyKey {
associatedtype Value
associatedtype Storage: Protocol
func value(in storage: some Storage) -> Value?
/// or... that regular generic clause
func setValue<T: Storage>(_ newValue: Value?, in storage: T)
}
Which gives me two errors:
Type 'some Storage' constrained to non-protocol, non-class type 'Self.Storage'
and:
Type 'T' constrained to non-protocol, non-class type 'Self.Storage'
How does one constrain a generic type to be a protocol? Clearly the Protocol
keyword is useless in this context. I've tried protocol
and that doesn't even parse enough to give helpful errors.
The type system seems to require a protocol constrained type but no way to actually constrain a type to be a protocol. What am I missing here?
protocol SomeProtocol {
associatedtype Value
associatedtype Container: // ???
func doWork<T: Container>(using container: T) -> Value?
}
How does one define a protocol that can define constraints for functions they require? Am I holding it wrong or am I trying to use a feature that doesn't come with this model?