I don't think this slope is all that slippery. I was looking at the actual set of implicit conversions modeled in the implementation, as well as the two recent pitches for adding conversions (this one, as well as mutable-to-immutable pointer conversions), and there is an overarching theme: C compatibility.
There are 21 different "conversion kinds" in that list, ignoring DeepEquality
which means the types are equivalent. Here's how they break down into categories:
Intrinsic to Swift subtyping:
Superclass
: subclass-to-superclass.Existential
: converting a concrete type to an existential type, e.g,Int
toEncodable
.MetatypeToExistentialMetatype
: converting a concrete metatype to an existential metatype, e.g.,Int.Type
toEncodable.Type
.ExistentialMetatypeToMetatype
: converting an existential metatype to a class metatype, e.g., because a generic parameter has a superclass bound.
Swift implicit conversions:
ValueToOptional
: convertingT
toT?
.OptionalToOptional
: convertingT?
toU?
whenT
can be converted toU
.
Motivated by C:
InoutToPointer
: allows passing&x
to a function taking a pointer, to aid in C interoperabilityArrayToPointer
: allows passing an array or address of an array to a function taking a pointer, to aid in C interoperabilityStringToPointer
: allows passing a string to a function taking a pointer toInt8
,UInt8
, orVoid
, to interoperate with C string functionsPointerToPointer
: internally models something like mutable-to-immutable pointer conversions, in support of the above four points.DoubleToCGFloat
:Double
->CGFloat
, motivated by theC
typedef forCGFloat
CGFloatToDouble
:CGFloat
->Double
, motivated by theC
typedef forCGFloat
Motivated by Objective-C:
ClassMetatypeToAnyObject
: This makesSomeClass.Type
convertible toAnyObject
, because Objective-C classes are objectsExistentialMetatypeToAnyObject
: Similar to the above, but for an existential meta typesProtocolMetatypeToProtocolClass
:SomeProtocol.Protocol
converts to the Objective-CProtocol
class.ArrayUpcast
: Implicit conversion from[T]
to[U]
whenT
converts toU
. We added this conversion because all of the imported Objective-C APIs in Swift 1.0 came in as[AnyObject]
(Objective-C generics didn't exist yet), and it caused a big impedance mismatch with Swift code because one always had to manually convert[SomeClass]
to[AnyObject]
to use any system frameworks. So, we added an implicit conversion to smooth it over.DictionaryUpcast
: Same as the above, but for dictionaries.SetUpcast
: Same as the above, but for sets.HashableToAnyHashable
: Implicit conversion from a value ofHashable
type toAnyHashable
, introduced in Swift 3 withAnyHashable
because of the impedance mismatch with untypedNSDictionary
parameters imported into Objective-C.CFTollFreeBridgeToObjC
: Implicit conversion from a toll-free-bridged CF type to its Objective-C class.ObjCTollFreeBridgeToCF
: The opposite of the above conversion.
Swift has a lot of hard-coded implicit conversions, but nearly all of them are motivated by improving interoperability with C and C-derived APIs. Indeed, outside of the basic subtyping relationship for classes and protocols (the first section above), the only implicit conversions that we have in Swift that weren't motivated by C interoperability are for optionals (the second section). And the two new proposals for implicit conversions on the table continue that trend by being primarily useful for improving the ergonomics of C interoperability. Even the integer-promotion proposals (allowing Int16
to implicitly convert to Int32
, for example) tend to be motivated by C APIs and C-derived data types.
This is why I don't buy the slippery-slope argument, because we're not adding implicit conversions to the "core" language. We're adding them for the same reason we always have, to make C interoperability more ergonomic because it's an important part of Swift's value proposition. I suspect this will naturally tail off because the surface area of C is more-or-less constant.
Your other argument is this:
This is the real discussion to be had. From my perspective, Swift very deliberately has a rigid type system with relatively few implicit conversions, and the implicit conversions we do have come from pragmatic decisions around reducing the impedance mismatch with (Objective-)C APIs. I do not want Swift to allow library authors to define their own implicit conversions, because I find reasoning about implicit conversions to require too much additional cognitive overhead. My experience in C++ with implicit conversions has been almost uniformly bad---it rewards API designs where concepts are split into many similar bespoke types whose differences are papered over with implicit conversions, and the presence of implicit conversions makes type inference less efficient and less predictable. Over time, I hope that the implicit conversions Swift already has will fade in relevance, as the C APIs we depend on get wrapped up or reimplemented in more-strongly-typed, more elegant Swift APIs.
Doug