A property wrapper will only be inferred as API if init(projectedValue:)
is declared directly in the nominal property wrapper type.
I believe we never actually define "nominal type", even with the current language definitions. IIRC @hborla infer in one of the comments that it's the original definition of the type (excluding extensions, even in the same module).
Maybe we want to instead use something like "original definition (excluding extensions)"?
Ok, so we can't convert Impl to API by introducing the init(projectedValue:)
outside of the nominal type. Instead, can we override the chosen init(projectedValue:)
/init(wrappedValue)
?
@propertyWrapper
struct Wrapper<Value> {
init(projectedValue: Value)
init(wrappedValue: Value)
}
extension Wrapper where Value == Int {
init(projectedValue: Value)
init(wrappedValue: Value)
}
func foo(@Wrapper x: Int)
Which ones would foo
use in this case? The only example in overload resolution of backing property wrapper initializer have the init
be in the nominal declaration, so it's somewhat ambiguous here.
How does the composition work when I mix the API and Impl wrappers?
func foo(@API @Impl x: Value)
func bar(@Impl @API x: Value)
foo
shouldn't be able to hide @Impl
since it's tied to the type of the projected value, we should still be able to hide it if API
only defines init(wrappedValue:)
.
bar
may be able to expose itself as func bar(@API x: Value)
using a similar reasoning.
Nonetheless, this sounds like a pretty complex composition rule.
Some rambling about Impl vs local var wrappers
I've been figuring out for the longest time the proper distinction between the Impl and local wrappers. It bothers me because similar features with subtle distinction are dangerous for the language as a whole. Said distinction confuses those that are new to both features and invites debates about best practices surrounding it. This has much less to do with the scope of the proposal. I'm not implementing it , so I couldn't care less if the proposal becomes too big, so long as it doesn't grow to a manifesto size.
If we look at the mutability, Impl is a poor feature since it only locks down the mutability. Property wrappers ought to be able to do it anyway (we'll need a separate proposal). Furthermore, if our recommendation is to "use Impl when you don't mutate, and use local var when you mutate", it goes against the Swift philosophy that mutability is easy to enable/disable, generally by swapping let/var
, and that invites creating local functions just to create Impl wrapper.
However, now that I start to internalize @John_McCall's interpretation (for lack of a better word):
I start to think that maybe the actual recommendation would be to "always use Impl when you wrapper argument and keep on using local wrapper elsewhere". This... sounds obvious in hindsight, but I can say that it's not easy to reach this conclusion. This makes me believe that there is just enough distinction for both Impl and local wrappers to live comfortably in the same language.