[swift-evolution] Proposal: Universal dynamic dispatch for method calls

Brent Royal-Gordon brent at architechies.com
Tue Dec 8 15:51:01 CST 2015


>> It seems
>> to me that, this case would be better served by some mechanism that makes
>> the non-override intent explicit at the point of declaration.
> 
> There is already such a mechanism. It's the fact that the declaration
> happens in an extension instead of in the original protocol.

No, what you’re saying is that there’s a simple rule governing which declarations are dispatched statically. This does not make it *explicit*. In the Swift 1 betas, there was a simple rule governing whether `if foo` was a nil check (is `foo` of type Optional?), but this was deemed insufficiently explicit and removed. Making the protocol extension behaivor *explicit* would mean, for instance, forcing the user to mark statically-dispatched protocol extension methods `final`, much like how you have to use `override` purely to reassure the compiler that you mean to do what your code says.

Given the tenor of the rest of the language, here’s how I would design this:

1. You can mark a protocol extension member as `final`. This means that, when casted to the protocol’s type, all instances will use *this* implementation, so it can safely be statically dispatched.

	protocol Foo {
		func bar()
	}
	extension Foo {
		final func baz() { … }
	}

2. Currently, all protocol extension members which aren’t listed in the protocol itself must be final:

	protocol Foo {
		func bar()
	}
	extension Foo {
		func baz() { … }	// error: declaration must be ‘final’.
	}

If a future version of the language were to allow overriding of extension methods, this rule would go away, and use of final would be purely voluntary.

3. Attempting to make a type conform to a protocol that has `final` members conflicting with its own causes an error:

	class ConcreteFoo {
		func baz() { … }
	}
	extension ConcreteFoo: Foo {	// error: Foo.baz() conflicts with ConcreteFoo.baz()
		func bar() { ... }
	}

4. The preferred way to handle these conflicts is to rename one of the members so they no longer conflict. However, if that isn’t possible, you can use a keyword to acknowledge and silence the error, either at the site of the conformance (preferred):

	extension ConcreteFoo: @incoherent Foo {

Or at the site of the extension (as a fallback for when someone else wrote the conformance):

	@incoherent(ConcreteFoo) extension Foo {
		final func baz() { … }
	}

It may also be necessary to allow it at the site of an import:

	import FooKit
	import ConcreteFooKit
	@incoherent(ConcreteFoo: Foo) import FooExtensions

I like “incoherent” because, to me, it expresses the symptom you’re going to see (viewing the object as a Foo will not give you the same members as viewing it as a ConcreteFoo). But @conflicting would be more obvious, and of course we could bikeshed this endlessly, in what appears to be the manner of our people.

-- 
Brent Royal-Gordon
Architechies



More information about the swift-evolution mailing list