[swift-evolution] [Pre-Proposal] Type Aliases as Pseudo-Types

Haravikk swift-evolution at haravikk.me
Sat Feb 18 04:18:30 CST 2017

This is an idea I had while working with collections, and is particularly inspired by those that use common index types.

Consider for example an array; for indices it simply uses an integer, however, while this is a perfectly valid type to use it opens up the possibility of integers from any number of different sources being passed in by mistake and causing run-time errors. The same is true for wrapping types that use AnyIndex, or really any type that uses Any* to hide underlying types, as on the surface all AnyIndex instances give the illusion of being compatible when they're not, and will only produce errors at run-time when a conflict arises.

The idea to combat this is simple; a new attribute that can be applied to a typealias, my working name is @unique, but there's probably a much better name for it. When applied to a type-alias it indicates to the type-checker that the type being aliased should be treated as a unique type outside of the scope in which it is declared.

For example:

	struct MyCollection<T> : Collection {
		@unique typealias Index = Int
		var startIndex:Index { return 0 }

	var foo = MyCollection<Foo>()
	var bar = MyCollection<Bar>()
	foo[bar.startIndex] // warning: incompatible types

Although the underlying type is an Int for both collections, and Ints can be used internally to satisfy methods/properties of type Index (i.e- within MyCollection the concrete type is still Int), externally indices are treated as a unique type of MyCollection<T>.Index, that just happens to have all the same methods, operators and properties as Int, minimising the risk of passing an index from the wrong source.

If you actually want to pass the "wrong" type you can still do it by casting:

	foo[bar.startIndex as MyCollection<Foo>.Index]

Bit verbose, but it makes absolutely clear that you want this to happen, rather than it happening by mistake. The same can also be done in reverse (to change an index to an Int).

Of course this isn't completely fool-proof, as two different instances of MyCollection<Foo> could still confuse indices as they're of the same pseudo-type, but in other cases it reduces the possibility of a mistake. It was the recent discussion on the .enumerate() method that reminded me of this, as a common mistake is to think that the offsets produced by enumerate are indices, just because they happen to be compatible with an Array, this could eliminate that as a mistake by forcing developers to see what's actually happening.

Currently to do something like this requires duplicating types, or using wrappers around Int just to make it appear to be different; it's not hard as such but there's no standard for it, so making it easier and more prevalent is desirable.

This shouldn't have any implications to ABI compatibility as it's a type-checker only feature (the code should still compile exactly as the same is now), but it's a partially source-breaking change in that if Array etc. are changed to use this feature then any code relying on Array indices being integers will need to add a cast. Since the types are still known to be compatible though a mismatch needn't be an error, a warning should suffice, in which case the change won't actually prevent compilation of existing code.
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://lists.swift.org/pipermail/swift-evolution/attachments/20170218/30103c0c/attachment.html>

More information about the swift-evolution mailing list