Swift makes implicit unwrapping a property of declarations
Swift's new snapshot implementation represents implicitly unwrapped values as ordinary optionals. The resulting changes to inference and member lookup explain several source compatibility surprises.
A value declared as String! has often encouraged the idea that it belongs to a special optional type whose behavior follows it through the program. Swift's new implementation removes that model. The value has type String?, and its declaration carries permission for the type checker to unwrap it when necessary.
Mark Lacey's April 26 account of the reimplementation says the work is available in recent development snapshots. It completes the implementation of SE-0054, which proposed replacing the distinct implicitly unwrapped optional type with an attribute on declarations. This is a report about the snapshot implementation, with Swift 4.1 behavior used as a comparison.
The distinction changes more than diagnostic spelling. It affects what a variable assignment can infer, which member a method call finds and whether a generic function receives an optional. These cases all follow from the same rule. The type checker first tries to use the ordinary optional value, then considers implicit unwrapping if the expression needs it.
I value Swift's explicit treatment of absence. Implicit unwrapping is useful at certain boundaries, but allowing its special behavior to spread through inference makes the boundary harder to locate. Attaching that permission to a declaration gives the rest of the program a more ordinary type to reason about.
Inference preserves the optional value
The new model can be seen without using a framework or imported API. A declaration may permit implicit unwrapping while a variable initialized from it acquires a plain optional type.
var original: Int! = 5
let copied = original
let required: Int = original
In the snapshot behavior described by Lacey, copied has type Int?. Assigning to it does not require the compiler to force the original value. The explicit Int annotation on required supplies a different context, so that assignment needs the implicit unwrap.
The type inferred for copied contains no permission to unwrap automatically at later uses. That permission belongs to the declaration of original. A subsequent use of copied must follow the ordinary rules for an optional integer. The change prevents implicit-unwrapping behavior from propagating just because a value passes through another binding.
Generic calls follow the same reasoning. An identity function can accept an optional as its generic argument without forcing it. Passing original through such a function therefore returns an optional. Generic inference does not need to invent a special implicitly unwrapped type.
Lacey also gives the example of storing a function whose declared result is implicitly unwrapped. The stored function value has an optional return type. Its inferred function type does not retain the declaration-specific behavior of the original function. This matters when higher-order code receives and calls that function later.
Member lookup can select a different map
The most revealing compatibility example involves map, because both Optional and Array provide that operation. Older behavior could unwrap an implicitly unwrapped array before calling the array's method. The new implementation first looks for a valid operation on the optional itself.
Consider the array declaration below. The two explicit expressions make different decisions about the outer optional.
class Item {}
let values: [Any]! = [Item()]
let optionalItems = values?.map { $0 as! Item }
let requiredItems = values!.map { $0 as! Item }
Optional chaining produces an optional array of items. Explicit force-unwrapping produces an array of items and requires the outer value to be present. Both expressions call the array's mapping operation after making the handling of the outer optional explicit.
The unqualified expression values.map { $0 as! Item } has a different interpretation under the new implementation. Optional's mapping operation gives its closure the entire wrapped array. The cast then attempts to turn an array into one Item. Lacey's example produces a warning that the cast always fails, but the expression technically passes type checking.
Because type checking succeeded on the optional interpretation, the compiler does not retry by unwrapping the array. This is a compatibility issue whose cause is lookup order and type checking. The closure's apparent intent does not override the first valid interpretation.
A superficially similar integer example continues to work as before. With an implicitly unwrapped integer array and a closure adding one, Optional's map cannot satisfy the expression. The compiler can then force the optional and call Array's map. Similar-looking source can therefore behave differently because only one closure permits the initial optional interpretation.
Diagnostics reflect the actual type
Errors now refer to T? for a declaration written with T!. That spelling is a consequence of the implementation. The declaration's underlying type is Optional, so a diagnostic that describes its type no longer needs a distinct implicitly unwrapped optional spelling.
The same reasoning removes an overload distinction. Swift 4.1 warns when two functions differ only by an inout Int? parameter versus an inout Int! parameter. In the new implementation, those declarations describe the same parameter type and produce an error. Removing the duplicate overload follows directly from representing both as ordinary optionals.
Code extending ImplicitlyUnwrappedOptional also loses its target type. Lacey explains that the old name is an unavailable type alias for Optional. An extension written for the former separate type cannot preserve a separate set of members. That removal helps explain why the new member-lookup behavior is more consistent.
The compiler retains special compatibility handling for deprecated casts written as as T!. It first attempts the optional interpretation, then attempts an explicit force of that optional if needed. The article warns that this remains deprecated. It is compatibility behavior around an old spelling, rather than evidence that the old type model survives.
Optional layers still matter
The change does not remove optional layers introduced by other operations. Lacey's try? example calls a throwing function whose result is implicitly unwrapped. The throwing operation introduces another optional layer, and an ordinary binding unwraps only one. Code can consequently retain an optional value inside a successful binding.
A switch binding can expose the same assumption. If a pattern captures an implicitly unwrapped input, the captured variable now has an ordinary optional type. Returning it where a non-optional string is required needs explicit handling. Lacey demonstrates both forcing the captured value and matching present and absent cases separately.
My preference is the version that states the absent case when absence is meaningful. The proposal retains implicit unwrapping for real interoperability and initialization needs, including Objective-C APIs with unspecified nullability. Its argument is that these needs do not justify a special type throughout the compiler and inferred program.
The snapshot implementation makes those choices more local. A declaration can still request implicit unwrapping, and an expression can still require it. Values inferred from that declaration otherwise remain ordinary optionals, so subsequent code must account for absence through the normal optional operations.