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.
_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution