This preliminary discussion covers the points A - F raised in the Swift Evolution Process at swift-evolution/process.md at master · apple/swift-evolution · GitHub.
Swift Evolution Process:
A. Review Commonly Rejected Proposal list. Done. No similar issues found.
B. Review Swift Evolution Forums. Done. Forum search at Search results for '' - Swift Forums. I found two potentially related proposals (see below). On further review, neither was applicable to this proposal.
- High Level ARC Memory Operations: not applicable
- Optional binding for upgrading self weak to strong #171: implemented 2016 Optional binding for upgrading self weak to strong by emaloney · Pull Request #171 · apple/swift-evolution · GitHub. This does not specifically cover the same topic as the proposal below.
C. Consider the goals of the upcoming Swift release. Done. Reviewed "On the Road to Swift 6" at On the road to Swift 6. There are three areas the Core Team is focusing on. This proposal supports issue #2, " Create a fantastic development experience". Specially the directive that "developers should be both highly productive and experience joy when programming in Swift".
On the Road to Swift 6:
#1: Accelerate growth of the Swift software ecosystem
#2: Create a fantastic development experience
#3: Invest in user-empowering language directions
D. Socialize the idea. Done. That's what this post is.
E. Develop the proposal. In progress. This is a set of future actions based on the response to this post.
F. Request a review. Deferred. This will be done only if the proposal is developed and there is sufficient interest in going forward.
The proposal below follows the Swift Proposal template at swift-evolution/0000-swift-template.md at master · apple/swift-evolution · GitHub.
==========
Proposal: SE-NNNN
Review Manager: TBD
Status: Not yet submitted for review
Title: Replace the reference types weak and unowned with nonstrong
Author: Patrick Weigel
Introduction
In Swift it is possible to create strong reference cycles between class instances. Strong reference cycles are bad as they create potentially crashing memory leaks (unused memory that is not made available to the app). Swift provides two ways to resolve strong reference cycles: weak references and unowned references. Swift requires the coder to determine which type (weak or unowned) to use, when in many instances the coder simply wants the type to be not strong.
Selecting the wrong type (weak or unowned) may itself cause a crashing error. This proposal replaces both weak and unowned with a new reference type, nonstrong, eliminating the crashing error.
See The Swift Programming Language (Swift 5.2), Chapter: Automatic Reference Counting, in the subchapters "Strong Reference Cycles Between Class Instances" and "Resolving Strong Reference Cycles Between Class Instances".
Motivation
There are two motivations: 1) Eliminate the crashing error that can be caused by incorrectly selecting weak or unowned; and 2) Reduce the barrier to entry to using Swift for beginning and intermediate coders.
There are two common causes of the default strong reference requiring manual intervention to eliminate strong reference cycles:
- Class A with a property of class B, and class B with a property of class A;
- Capturing self in a completion block.
The solution is for the coder to manually identify one of the properties or the self with either weak or unowned. The decision to use weak versus unowned is determined as outlined in “The Swift Programming Language (Swift 5.2).” Apple Books. This decision appears to be related to either:
- “Because a weak reference does not keep a strong hold on the instance it refers to, it’s possible for that instance to be deallocated while the weak reference is still referring to it.'; or
- Whether or not the “the other instance has the same lifetime or a longer lifetime”
In practice, the rule of thumb is if the property is an optional, use weak. If the property is nonoptional, use unowned.
Proposed solution
Why force the coder to either decipher the description in “The Swift Programming Language", or to remember the rule of thumb? This decision should be made by the compiler once the coder indicates that they do not want a strong reference. Thus the reference type nonstrong would be interpreted by the compiler as weak when that is appropriate, and unowned when that is appropriate.
From The Swift Programming Language (Swift 5.2), Automatic Reference Counting, in the subchapters "Strong Reference Cycles Between Class Instances" and "Resolving Strong Reference Cycles Between Class Instances" the current solution is to:
class Apartment
weak var tennant: Person?
and
class CreditCard
unowned let customer: Customer
The current solution outlined above in this proposal will be replace with the new code below, using nonstrong:
class Apartment
nonstrong var tennant: Person?
and
class CreditCard
nonstrong let customer: Customer
This solution (nonstrong) is safer than the existing method (weak and unowned) as it results in the compiler implementing the weak or unowned reference type, and removes the possibility that the coder will select the wrong reference type.
The current solution (weak and unowned) creates the potential for crashing bugs, which is the situation that the solution was attempting to correct. The weak and unowned solution can create crashing bugs if the coder selects the wrong type. The strong reference cycle creates crashing bugs via memory leaks.
Detailed design
This proposed solution does not involve new syntax, nor is it a new API.
However, it is NOT an additive change, as both weak and unowned will be replaced by nonstrong.
The design is:
-
Disallow "weak" and "unowned" as reference modifiers, these will become compiler errors;
-
Allow "nonstrong" as a reference modifier;
-
When "nonstrong" is encountered, use the logic in “Resolving Strong Reference Cycles Between Class Instances”, from Apple Inc. “The Swift Programming Language (Swift 5.2).” to determine whether to treat "nonstrong" as "weak" or "unowned".
Source compatibility
This change is a breaking change to existing Swift 5 code. The existing construct (weak, unowned) is harmful as it forces the coder to do work that rightfully should be done by the compiler.
The volume of affected Swift 5 code is not small, but we can (with significant work) provide an automated migration path.
Existing correct Swift 5 code will stop compiling due to this change. It is possible to continue to allow weak and unowned, see "Alternatives Considered" for why in this instance backwards compatibility is not optimal.
It is possible (with significant work) to automatically migrate from the old syntax to the new syntax.
Effect on ABI stability
I do not believe that this proposal changes the application binary interface (ABI) of existing language features. As the proposal author I'm not familiar enough with this issue to comment authoritatively, I welcome any additional input on this matter. If this proposal needs to be changed significantly or if there is significant work to investigate and document this ABI issue, such effort should result in co-authorship (if desired).
Effect on API resilience
I do not believe that this proposal changes the resilience of the application binary interface (ABI). Similar to the API stability section, I welcome any additional input on this matter.
Alternatives considered
-
Leave current implementation as is.
-
Wait until memory is zero-cost, so that memory leaks are irrelevant.
-
Handle memory leaks due to strong reference cycles some other way.
-
Leave weak and unowned as is, add nonstrong.
-
Use "notstrong" rather than "nonstrong"
-
Leave current implementation as is.
This is the most obvious alternative, and will create the fewest problems short term. However, long term the existence of coders thinking "weak/unowned, WTF?" and "Well I'll just try weak and see what happens" violates the founding principles of Swift: "Swift makes it easy to write software that is incredibly fast and safe by design. Our goals for Swift are ambitious: we want to make programming simple things easy, and difficult things possible." (https://swift.org). -
Wait until memory is zero-cost, so that memory leaks are irrelevant.
This is the the easiest alternative, but does not resolve this issue short term. -
Handle memory leaks due to strong reference cycles some other way.
If this is the correct solution, it will require a different Swift Evolution proposal. -
Leave weak and unowned as is, add nonstrong.
This is the most attractive of the alternatives considered, but results in the accumulation of unneeded cruft in Swift. The default assumption of backwards compatibility will hold back Swift by resulting in a large, unwieldy language and in an increase in the amount of mental overhead required by coders. -
Use "notstrong" rather than "nonstrong".
Alternatives to the keyword "nonstrong" should be considered.