Skip to content

Instantly share code, notes, and snippets.

@anandabits
Created February 11, 2018 21:30
Show Gist options
  • Star 7 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save anandabits/bd73521e0c5c06371f4a268ab8c482c9 to your computer and use it in GitHub Desktop.
Save anandabits/bd73521e0c5c06371f4a268ab8c482c9 to your computer and use it in GitHub Desktop.
Emulating closed protocols in Swift
// This example shows how closed protocols can be emulated in Swift today.
// The technique leverages Swift's support for public (but not open) classes.
// First, it's worth observing that there is an almost trivial technique that can be used when
// it is possible to specify a (possibly abstract) superclass for all conforiming types.
// Simply declare a public (not open) base class and a protocol with a Self inheritance constraint.
// Swift does not support open subclasses of a public superclass so no classes outside the module
// will be able to meet the self inheritance constraint.
public class FooBase {}
public protocol Foo where Self: FooBase {}
// Now we can declare a set of conforming (and potentially non-conforming) subclasses that cannot be extended outside the module.
public class Foo1: FooBase, Foo {}
private class Foo2: FooBase, Foo {}
public class Foo3: FooBase {}
func takesFoo<F: Foo>(_ foo: F) {
switch F.self {
case is Foo1.Type: print("foo1")
case is Foo2.Type: print("foo2")
default: fatalError("unfortunately the exhaustiveness checker is not as smart as we would like it to be.")
}
}
takesFoo(Foo1()) // "foo1"
takesFoo(Foo2()) // "foo2"
// This is a nice technique but it is also very limited.
// Fortunately, it can be leveraged to support arbitrary closed protocols
// at the cost of an associated type in the "closed" protocol
// and a set of classes parallel to the conforming types.
/// A non-open base class for classes that run parallel to the types conforming to a closed protocol.
///
/// The _ in the name indicates that while this class must be public it is an implementation detail
/// that should be ignored by users of the module.
public class _BarValidatorBase {}
/// A protocol with a Self inheritance constraint requiring conforming types to inherit from `_BarValidatorBase`.
/// Classes which conform to this protocol validate that a specified type is allowed to conform to the protocol.
///
/// The _ in the name indicates that while this class must be public it is an implementation detail
/// that should be ignored by users of the module.
public protocol _BarValidator where Self: _BarValidatorBase {
/// A type that is allowed to conform to the "closed" protocol.
associatedtype Validated
}
/// A protocol for which no conformances are possible outside the declaring module.
/* closed */ public protocol Bar {
/// An associated type requiring the presence of a class that "validates" that a type is allowed to conform to `Bar`.
///
/// The _ in the name indicates that while this class must be public it is an implementation detail
/// that should be ignored by users of the module.
///
/// Unfortunately, the presence of this associated type precludes the use of this protocol
/// as an existential type until support for generalized existentials are added to Swift.
/// The good news is that when such support is added it should be possible to use the protocol as if this associated type
/// did not exist at all (aside from the obvious need to specify it in conformances provided by the module).
associatedtype _Validator: _BarValidator where _Validator.Validated == Self
}
/// A struct that conforms to the "closed" protocol.
public struct Bar1: Bar {
/// In order to conform, `Bar1` is required to specify a `_Validator` whose `Validated == Bar1`.
/// A nested class is the simplest way to do this.
public class _Validator: _BarValidatorBase, _BarValidator {
public typealias Validated = Bar1
}
}
/// A generic struct that conforms to the "closed" protocol.
public struct GenericBar<T>: Bar {
/// As with a non-generic type, a nested validator is used to validate the conformance.
public class _Validator: _BarValidatorBase, _BarValidator {
public typealias Validated = GenericBar<T>
}
}
/// Classes can of course also conform to this protocol if desired.
public class BarClass: Bar {
/// As with a non-generic type, a nested validator is used to validate the conformance.
public class _Validator: _BarValidatorBase, _BarValidator {
public typealias Validated = BarClass
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment