Skip to content

Swift 5.0 playground with a cautionary example of a type-erasing wrapper along with detailed discussion

License

Notifications You must be signed in to change notification settings

dan-rodrigues/swift-type-erasure-playground

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

1 Commit
 
 
 
 
 
 
 
 

Repository files navigation

Swift Type-Erasure Playground

Background

The Swift version at time of writing (5.x) has restrictions around generics regarding existentials which are covered in this part of the GenericsManifesto. These restrictions motivate users to define their own type-erasing wrappers when the concrete type is to be hidden or when there is a need to store some conforming instance without referring to any given concrete type.

Motivation

The mentioned type-erasing wrappers cover many cases but it's not currently possible to cover all cases as demonstrated in this playground. A major problem with this as that the author can implement a wrapper that apparently works for simpler use cases without realizing this, until much later when bugs become apparent. Because these bugs are only noticed at runtime without any errors or warnings at build time, they may be difficult to debug when they do arise.

Discussion

There are several type-erasing wrappers included in the Swift standard library such as the ones listed here. The one focused on this is playground is AnyHashable along with a problematic attempt to implement a user-defined equivalent, called SomeHashable. An edited portion of the playground is included below which summarizes the issue.

class Super: Hashable { /* includes Hashable conformance and initializer */ }

class SubA: Super {}
class SubB: Super {}

// Baseline: using the == operator directly, uses Equatable conformance included in Super
SubA(1) == SubB(1) // true
SubA(2) == SubB(3) // false

// AnyHashable: works as above
AnyHashable(SubA(1)) == AnyHashable(SubB(1)) // true
AnyHashable(SubA(2)) == AnyHashable(SubB(3)) // false

// SomeHashable: doesn't work as above for reasons detailed in the playground
SomeHashable(SubA(1)) == SomeHashable(SubB(1)) // false (attempted to cast SubB instance to SubA, fails)

// SomeHashable + type coercion hack: "works" but has many downsides, wouldn't recommend
SomeHashable(SubA(1) as Super) == SomeHashable(SubB(1) as Super) // true (casting SubB instance to Super, succeeds)

AnyHashable doesn't suffer from these issues thanks to this supporting C++ code that effectively performs the common superclass search at runtime, then calling the == implementation of that common superclass. The resulting behaviour is the same as using the == operator directly.

There is currently no way to perform this sort of dynamic upcast in pure Swift code which prevents user-defined types from doing the same as AnyHashable.

Most of the commentary is in playground source itself. The contents of this README is just a brief summary.

Tools

Xcode 12.5.1 was used to create the playground.

About

Swift 5.0 playground with a cautionary example of a type-erasing wrapper along with detailed discussion

Topics

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages