SE-0258: Property Wrappers (third review)

It's been a requested future direction that programmers be able to supply the storage themselves, yes. (Presumably it could be computed — or an alias to other storage, if we add such a feature.)

5 Likes

The problem with this is that SwiftUI encourages the use of default initializers on structs. Making users write explicit generic initializers for all their bindable views would make it a lot less visually lightweight.

(In other words, projections are being used as a hack around the absence of extensible implicit conversions in Swift. This could easily have been achieved with a prefix operator, but here we are.)

2 Likes

I have a question about property wrappers and protocols, also related to the hiding of the wrapper as an implementation detail. In beta 2, it looks like the following is disallowed:

protocol Foo {
    @Published var bar: Int { get } // error: Property 'bar' declared inside a protocol cannot have a delegate
}

If we are always considering the wrapper type to be an implementation detail, it makes sense that this would be disallowed. But this is also disallowed:

protocol Foo {
    var bar: Int { get }
    var $bar: Published<Int> { get } // error: Cannot declare entity '$bar' with a '$' prefix
}

This would have the nice advantage that any wrapper type which supplies the proper projection could be substituted in for Published, even though it makes the protocol definition a bit more verbose.

Is there any plan to support this? If projections are "equal in importance to the wrapped value," it seems like there should be some way to specify in a protocol that there must be a certain projection available. The workaround of adding a var publishedBar: Published<Bar> requirement dilutes the intent of the protocol and makes conforming to it cumbersome. Is there a different way to achieve this that I'm not seeing?

3 Likes

The provided initializers which take a State<_> would be there only in SwiftUI-owned types for compatibility with existing WWDC sample code and sessions. Users of SwiftUI would be free to copy this paradigm if they want, but I was imagining that the canonical way would just be to force users to write $foo.binding if they wish to create a binding to their state.

Sure, but we already know that isn’t going to happen.

1 Like

Suggestions:

  1. Could the same name be used for wrapping and unwrapping?
    i.e. init(wrappedValue: T) and var wrappedValue: T.

  2. Or could there be an unlabelled initializer to wrap, and a no-parameter subscript to unwrap?
    i.e. init(_: T) and subscript() -> T.

Overall +1

I am concerned about the private backing storage taking on a valid identifier that can collide with other identifiers that start with _

Would you consider _$myprop ?

Can you or other that share the same concern elaborate on why this is an issue? Can you provide a concrete example why or where such collision would ever occur? Why would you have a wrapped property foo but also a totally different _foo property which is unrelated?

4 Likes

I've been following the development of this feature from the start and would like to thank the core team and proposal authors for being receptive to the feedback.

After reading 3rd proposal and looking over the implementation, I am a +1. Specifically, very happy to see the following feedback taken into consideration:

While there are multiple opinions from community members on things quoted above, proposed changes resonate with me personally.

I also agree with this rationale.

Cheers!

Hi @DevAndArtist,

Can you or other that share the same concern elaborate on why this is an issue?

See below

Can you provide a concrete example why or where such collision would ever occur?

See you question below.

Why would you have a wrapped property foo but also a totally different _foo property which is unrelated?

Why not?

If that is the only explanation for your (but maybe not others) concern then I don‘t understand how something like this can be justified as a dealbreaker.

I still would like to see anyone providing a concrete code example where it would be critical to have a wrapped property but also a totally different and unrelated property that shares the exact same identifier as proposed for private backing storage which then would collide.

5 Likes

Thank you for your hard work.

A.swift
class Test {
    @MyWrapper var t = ""
}

private func test() {
    let d = \Test.$t
}

B.swift
private func test() {
//    let t = Test()
    let d = \Test.$t // cannot access
}

C.swift
private func test() {
    let t = Test()
    let d = \Test.$t // can access
}

Is this a bug?

1 Like

Yes, definitely, we'll try to get it fixed.

2 Likes

Please file a bug report if you haven‘t already.

SR-11046

2 Likes

Is there any plan to use Property Wrapper to enable simpler Codable usage where one wants to specify a JSON key being different from the name of just one property?

That would really be awesome, because AFAIK currently Codable Dora not offer me to use the synthesized init/encode function and keys if I wanna change just one key. For bigger types, with e.g. 10 properties (JSON fields) this is very cumbersome. Property Wrapper might solve this?

You don‘t need real world examples to dislike names that could (theoretically) collide - imho that is a question that is more fundamental.

Objective-C has a history of „magic“ names (property setters, and maybe more), but imho it‘s no good fit for Swift when changing names has side effects on other properties of a type.
Take inherited methods as example:
How likely is it that you implement a totally different and unrelated method with an identical name?
Still, Swift demands that you override explicitly.

Memory layout is another example: It is actually a property of a given type - but afair, it isn‘t modeled that way because of possible interference with static properties.

If we would reject every SE proposal just because some people say "why not?" as an explanation for their concern we wouldn‘t have a language we have today.

If people cannot present any possible concrete examples (even hypothetical pseudocode), I don‘t buy these contra arguments. To be clear I only speak for myself here.

Any form of identifier name for the backing storage is a subject for a collision because "who says that in the future such identifier won‘t be allowed as valid user code"!?

8 Likes

Every variable may collide with an other variable. If you encounter such case, just do as you do right now, change the name of one of the two variables. That's not like if the compiler where imposing you a fixed name on all types (what it would do for memory layout).
We are talking about a name that can be changed easily as it is based on the wrapped variable name, and that will be generated only when a type contains a property wrapper.

1 Like

It's in the future direction (Delegating to an Existing Property) for you to opt-out of the default storage name, should you wish to.

2 Likes