Type Erasure in Swift
mikeash.com
mikeash.com
> func f(seq: Sequence<Int>) { ...
> […] Swift provides the AnySequence type to solve this problem. AnySequence wraps an arbitrary Sequence and erases its type, providing access to it through the AnySequence type instead. Using this, we can rewrite f and g:
> func f(seq: AnySequence<Int>) { ...
> […] The Swift standard library has a bunch of these Any types, such as AnyCollection, AnyHashable, and AnyIndex.
Please excuse my ignorance, but what’s the point of this?
Have all these <SomeType> and their Any<SomeType> counterpart seems like unnecessary code duplication to me.
Ideally this would not be necessary, and in some future version of Swift it probably won't be, but for now it is.
For another type to reference a generic type, it would either have to be generic itself, use a type erased `AnySomeGenericType<Type>` reference, or specify the exact version that's being used, e.g. ConcreteGenericType<Type>, which makes your code wayyyyy less abstract and entangled with another type.
This is very much something the language should handle, it just hasn't yet, so you have to write these large complicated boxing and unboxings.
What's the reason for that? In Java, for example, interfaces can be generic in just the same way concrete types can, which also means you can declare variables and parameters of some specific instantiation of the interface. It looks like Swift has two different generics systems, one for concrete types and one for protocols, leading to this extra complexity. Presumably there's some reason for doing things this way.
There are cases where this is not possible, like when you are returning a sequence from a function whose API you don't want to bind to a specific type. Then the only thing you can do is to pass an AnySequence.
fn f(seq: &mut Iterator<Item=&u8>)
although due to object-safety constraints they may not be suitable for every case.Swift forbids existentials when the protocol has an associated type. Rust has a notion of "object safety" which is both more flexible and more complicated, but applies the same restriction in most cases.
I think the closest analog of Swift's `AnySequence<Int>` would be a `Box<Iterator<Item=Int>>`.
I think Rust can do this case. I think this is how a simple version of `collect` would work (or very nearly).
This isn't quite a fair comparison. Manual memory management increases the risk of producing incorrect or buggy programs. Satisfying a strict compiler increases the chance that your program is written correctly.
And for what it's worth, this boilerplate dance for "type erasure" will be solved with what the Swift team calls generalized existentials, where you'll be able to use nominal types like Any<Sequence where .Iterator.Element == String>. "Type erasure" isn't the result of "aiming so hard for perfection" as you say, but because Swift is still a young language and there is only so much the creators can work on at any one time. Most Swift devs (think iOS apps) aren't running up against this problem much, and I haven't seen a single situation in an app where implementing a "type erased" generic type was necessary. At this point in the Swift timeline, just making Swift code compile faster would make devs much happier.
> it's still a mine field of exceptions
Unlike languages that actually use exceptions, which really are mine fields of exceptions in a different sense :P
rust took a few very opinionated design decisions (eg : strong safety in concurrency), and derived everything else from those principles, trying to reach something "user friendly" in the end, and is not there yet.
Swift always claimed to be a very "pragmatic" language, not sacrificing user convenience to purity. The problem is that they grabbed good ideas a bit everywhere, but still struggle to reach enough power in the language to become truly user friendly when the user start using all those bits to solve their particular problem (eg : generics and protocols, or concurrency, or typed exception, or result type, etc.).