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

Reply via email to