When we introduced Swift, we wanted to provide value types for common 
containers, with the safety and state isolation benefits they provide, while 
still working well with the reference-oriented world of Cocoa. To that end, we 
invested a lot of work into bridging between Swift’s value semantics containers 
and their analogous Cocoa container classes. This bridging consisted of several 
pieces in the language, the compiler, and the runtime:

Importer bridging, importing Objective-C APIs that take and return NSString, 
NSArray, NSDictionary and NSSet so that they take and return Swift’s analogous 
value types instead.

Originally, the language allowed implicit conversions in both directions 
between Swift value types and their analogous classes. We’ve been working on 
phasing the implicit conversions out—we removed the object-to-value implicit 
conversion in Swift 1.2, and propose to remove the other direction in 
SE–0072—but the conversions can still be performed by an explicit coercion 
string as NSString. These required-explicit as coercions don’t otherwise exist 
in the language, since as generally is used to force coercions that can also 
happen implicitly, and value-preserving conversions are more idiomatically 
performed by constructors in the standard library.

The runtime supports dynamic bridging casts. If you have a value that’s 
dynamically of a Swift value type, and try to as?, as!, or is-cast it to its 
bridged Cocoa class type, the cast will succeed, and the runtime will apply the 
bridging conversion:

// An Any that dynamically contains a value "foo": String
let x: Any = "foo"
// Cast succeeds and produces the bridged "foo": NSString
let y = x as! NSString 
Since Swift first came out, Cocoa has done a great job of “Swiftification”, 
aided by new Objective-C features like nullability and lightweight generics 
that have greatly improved the up-front quality of importer-bridged APIs. This 
has let us deemphasize and gradually remove the special case implicit 
conversions from the language. I think it’s time to consider extricating them 
from the dynamic type system as well, making it so that as?, as!, and is casts 
only concern themselves with typechecks, and transitioning to using standard 
initializers and methods for performing bridging conversions. I’d like to 
propose the following changes:

Dynamic casts as?, as! and is should no longer perform bridging conversions 
between value types and Cocoa classes.
Coercion syntax as should no longer be used to explicitly force certain 
bridging conversions.
To replace this functionality, we should add initializers to bridged value 
types and classes that perform the value-preserving bridging operations.
The Rules of as[?]

Our original goal implementing this behavior into the dynamic casting machinery 
was to preserve some transitivity identities between implicit conversions and 
casts that users could reason about, including:

x as! T as! U === x as! U, if x as! T succeeds. Casting to a type U should 
succeed and give the same result for any derived cast result.
x as! T as U === x as! U. If T is coercible to U, then you should get the same 
result by casting to Tand coercing to U as by casting to U directly.
x as T as! U === x as! U. Likewise, coercing shouldn’t affect the result of any 
ensuing dynamic casts.
x as T as U === x as U.
The interaction of these identities with the bridging conversions, as well as 
with other type system features like implicit nonoptional-to-Optional 
conversion, occasionally requires surprising behavior, for instance the 
behavior of nil Optional values in https://github.com/apple/swift/pull/1949. 
These rules also inform the otherwise-inconsistent use of as to perform 
explicit bridging conversions, when as normally only forces implicit 
conversions. By simplifying the scope of dynamic casts, it becomes easier to 
preserve these rules without bugs and unfortunate edge cases.

The Abilities of as? Today

In discussing how to change the behavior of dynamic casts, it’s worth 
enumerating all the things dynamic casts are currently able to do:

Check that an object is an instance of a specific class.

class Base {}; class Derived: Base {}

func isKindOfDerived(object: Base) -> Bool {
  return object is Derived
}

isKindOfDerived(object: Derived()) // true
isKindOfDerived(object: Base()) // false
Check that an existential contains an instance of a type.

protocol P {}
extension Int: P {}
extension Double: P {}

func isKindOfInt(value: P) -> Bool {
  return value is Int
}
isKindOfInt(value: 0) // true
isKindOfInt(value: 0.0) // false
Check that a generic value is also an instance of a different type.

func is<T, U>(value: T, kindOf: U.Type) -> Bool {
  return value is U
}

is(value: Derived(), kindOf: Derived.self) // true
is(value: Derived(), kindOf: Base.self) // true
is(value: Base(), kindOf: Derived.self) // false
is(value: 0, kindOf: Int.self) // true
Check whether the type of a value conforms to a protocol, and wrap it in an 
existential if so:

protocol Fooable { func foo() }

func fooIfYouCanFoo<T>(value: T) {
  if let fooable = value as? Fooable {
    return fooable.foo()
  }
}

extension Int: Fooable { func foo() { print("foo!") } }

fooIfYouCanFoo(value: 1) // Prints "foo!"
fooIfYouCanFoo(value: "bar") // No-op
Check whether a value is _ObjectiveCBridgeable to a class, or conversely, that 
an object is _ObjectiveCBridgeable to a value type, and perform the bridging 
conversion if so:

func getAsString<T>(value: T) -> String? {
  return value as? String
}
func getAsNSString<T>(value: T) -> NSString {
  return value as? NSString
}

getAsString(value: "string") // produces "string": String
getAsNSString(value: "string") // produces "string": NSString

let ns = NSString("nsstring")
getAsString(value: ns) // produces "nsstring": String
getAsNSString(value: ns) // produces "nsstring": NSString
Check whether a value conforms to ErrorProtocol, and bridge it to NSError if so:

enum CommandmentError { case Killed, Stole, GravenImage, CovetedOx }

func getAsNSError<T>(value: T) -> NSError? {
  return value as? NSError
}

getAsNSError(CommandmentError.GravenImage) // produces bridged NSError
This is what enables the use of catch let x as NSError pattern matching to 
catch Swift errors as NSErrorobjects today.

Check whether an NSError object has a domain and code matching a type 
conforming to _ObjectiveCBridgeableErrorProtocol, and extracting the Swift 
error if so:

func getAsNSCocoaError(error: NSError) -> NSCocoaError? {
  return error as? NSCocoaError
}

// Returns NSCocoaError.fileNoSuchFileError
getAsNSCocoaError(error: NSError(domain: NSCocoaErrorDomain,
                                 code: NSFileNoSuchFileError,
                                 userInfo: []))
Drill through Optionals. If an Optional contains some value, it is extracted, 
and the cast is attempted on the contained value; the cast fails if the source 
value is none and the result type is not optional:

var x: String? = "optional string"
getAsNSString(value: x) // produces "optional string": NSString
x = nil
getAsNSString(value: x) // fails
If the result type is also Optional, a successful cast is wrapped as some value 
of the result Optional type. nil source values succeed and become nil values of 
the result Optional type:

func getAsOptionalNSString<T>(value: T) -> NSString?? {
  return value as? NSString?
}

var x: String? = "optional string"
getAsOptionalNSString(value: x) // produces "optional string": NSString?
x = nil
getAsOptionalNSString(value: x) // produces nil: NSString?
Perform covariant container element checks and conversions for Array, 
Dictionary, and Set.

There are roughly three categories of functionality intertwined here. (1) 
through (4) are straightforward dynamic type checks. ((4) is arguably a bit 
different from (1) through (3) in that protocol conformances are extrinsic to a 
type, whereas (1) through (3) check the intrinsic type only of the 
participating value.) (5) through (7) involve Cocoa bridging conversions. (8) 
and (9) reflect additional implicit conversions supported by the language at 
compile time into the runtime type system. Optional and covariant container 
conversions have also been criticized as occasionally surprising and 
inconsistent with the rest of the language. If we curtail these conversions in 
the compiler, we would also want to consider removing their special dynamic 
cast behavior too. For the purposes of this discussion, I’d like to focus on 
removing the bridging behavior, cases (5) through (7).

Replacements for Dynamic Cast Behavior

If we remove bridging behavior from dynamic casts, we still need to provide API 
for performing those conversions. I’d recommend introducing unlabeled 
initializers for these conversions, matching the conventions for other 
value-preserving conversions in the standard library:


extension String {
  init(_ ns: NSString)
}
extension NSString {
  init(_ value: String)
}
extension Array where Element: _ObjectiveCBridgeable {
  init(_ ns: NSArray<Element._ObjectiveCType>)
}
extension NSArray {
  init<BridgedElement: _ObjectiveCBridgeable
       where BridgedElement._ObjectiveCType == Element>(
    _ value: Array<BridgedElement>)
}
/* etc. */

NSError bridging can also be extracted from the runtime, and the same 
functionality exposed as a factory initializer on NSError:


extension NSError {
  init(_ swiftError: ErrorType)
}

It’s also useful to be able to conditionally bridge to a value type from 
AnyObject, especially when working with heterogeneous property lists from 
Cocoa. This could be handled using failable initializers:


extension String {
  init?(bridging: AnyObject)
}
extension Array {
  init?(bridging: AnyObject)
}
extension Dictionary {
  init?(bridging: AnyObject)
}
/* etc. */

(This can probably be factored into a protocol extension on 
_ObjectiveCBridgeable.) Similarly, one could add a failable initializer to 
ErrorProtocol for bridging NSErrors back to Swift error values:


extension ErrorType {
  init?(bridging: NSError)
}

If you want to get really reductionist, you can ask whether as? and related 
operations really need special syntax at all; they could in theory be fully 
expressed as global functions, or as extension methods on Any/AnyObject if we 
allowed such things. Regardless, I think we want type-checking dynamic casts to 
be clearly a different operation from these bridging conversions. This will 
lead to a cleaner, easier-to-understand model with less special-case magic 
behavior.

-Joe
_______________________________________________
swift-evolution mailing list
[email protected]
https://lists.swift.org/mailman/listinfo/swift-evolution

Reply via email to