Skip to content

Commit

Permalink
Edit alternatives considered.
Browse files Browse the repository at this point in the history
  • Loading branch information
Amritpan Kaur committed May 15, 2023
1 parent 0a6ac8d commit 6335616
Showing 1 changed file with 42 additions and 21 deletions.
63 changes: 42 additions & 21 deletions proposals/0383-allow-let-property-wrapper.md
Original file line number Diff line number Diff line change
Expand Up @@ -128,16 +128,18 @@ struct ContentView: View {

Similarly, other SwiftUI property wrappers could be `let` declared when they do not require more than a single initialization.

### Property wrappers with `nonmutating set`
## Alternatives considered

Property wrappers that have a `wrappedValue` property with a `nonmutating set` (e.g., SwiftUI's [`@State`](https://developer.apple.com/documentation/swiftui/state/wrappedvalue) and [`@Binding`](https://developer.apple.com/documentation/swiftui/binding/wrappedvalue)) will preserve the reference semantics of the `wrappedValue` implementation even when marked as a `let` declaration. For example:
### @State and @Binding property wrappers with `nonmutating set`

Currently, SwiftUI offers two property wrappers, [`@State`](https://developer.apple.com/documentation/swiftui/state/wrappedvalue) and [`@Binding`](https://developer.apple.com/documentation/swiftui/binding/wrappedvalue)) that have a `wrappedValue` property with a `nonmutating set`. This allows the backing types to preserve the reference semantics of the `wrappedValue` implementation.

A `var` declared `@State` property currently generates a mutating set that reflects `@State`'s reference based storage:

```swift
@State let weekday: String = "Monday"
```
Here, `weekday` is an immutable instance of the `@State` property wrapper, but its `wrappedValue` storage will retain its mutability and reference type traits. This will translate to:
```swift
private let _weekday: State<String> = State<String>(wrappedValue: "Monday")
@State var weekday: String = "Monday"

private var _weekday: State<String> = State<String>(wrappedValue: "Monday")
var weekday : String {
get {
return _weekday.wrappedValue
Expand All @@ -149,31 +151,50 @@ var weekday : String {
yield ()
}
}
var $weekday: Binding<String> {
get { return _weekday.projectedValue }
}
```

Marking `weekday` as a let declared property will not remove access to its `wrappedValue`'s nonmutating set and the `wrappedValue` can be assigned via the backing property, `_weekday`:
Declaring the `@State` property with a `let` will remove the mutating setter:

```swift
_weekday.wrappedValue = "Tuesday"
@State let weekday: String = "Monday"

private let _weekday: State<String> = State<String>(wrappedValue: "Monday")
var weekday : String {
get {
return _weekday.wrappedValue
}
}
```

However, this does not affect the immutability of `weekday`, which can only be assigned to once like any ordinary let wrapped property. Any attempt to reassign `weekday` will result in an error:
[There is an argument to be made](https://forums.swift.org/t/pitch-allow-property-wrappers-on-let-declarations/61750/20) in favor of retaining the `mutating set` for let declare @State and @Binding to signify their reference based storage. Afterall, a property wrapper's `wrappedValue` could be assigned via the `_weekday` backing property, even if `weekday` was marked with a `let` instead of a `var`.

However, we have elected to remove the `mutating set` for `let` declared `@State` or `@Binding` to maintain language consistency. Since property wrapper is just a struct or class with an attribute and a wrappedValue, a struct with a backing type of a class would not generate a mutating setter for its instances. For example:

```swift
weekday = "Wednesday" // Error: Cannot assign to value: 'weekday' is a 'let' constant
```
class A {
init () {}
}

## Source compatibility
struct B {
var num: A

This is an additive feature that does not impact source compatibility.
init(n: A) {
self.num = n
}
}

## Effect on ABI stability
struct Test {
let b: B // note: change 'let' to 'var' to make it mutable

var test: A {
get { b.num }
nonmutating set { b.num = newValue } // error: cannot assign to property: 'b' is a 'let' constant
}
}
```

This is an additive change that has no direct impact that compromises ABI stability.
Should SwiftUI or general property wrapper usage evolve in a different direction in the future, this decision can be reconsidered to accommodate backing type traits in instances.

## Effect on API resilience
## Effect on ABI stability/API resilience

This is an additive change that has no impact on API resilience.
This is an additive change that does not impact source compatibility, does not compromise ABI stability or API resilience, and requires no runtime support for back deployment.

0 comments on commit 6335616

Please sign in to comment.