It isn't solely about performance. As you note, existentials and type erasers have fundamental limitations of their own by their nature. AnySequence
could achieve some of the benefit of hiding the implementation type, but it wouldn't be possible for an existential or type eraser to ever provide conditional conformances to fully express the interface of collections. Granted, this initial revision of opaque result types doesn't either, but it could get there in time.
Even for a relatively trivial protocol example like Shape
, where type erasure could be a workable approach, there are benefits to preserving and enforcing type structure beyond performance. Using AnyShape
in the example would allow an implementation of GameObject
to return arbitrarily different shape compositions in different conditions:
struct Spinner: GameObject {
var shape: /*any*/ Shape {
if angle == 0 { return Rectangle() }
return Transform(Rectangle(), by: Rotation(angle))
}
}
If the consuming framework does animation interpolation or anything like that, the interpolator could be extremely confused if it got very different shape graphs between ticks. By encouraging objects to return a consistent shape type, the interface also encourages a consistent shape structure, which makes higher-level reasoning about objects easier.
Without a constraint, the associated types aren't known, beyond being specific types related to the opaque return type. This is like accepting a generic argument constrained to Collection
without further elaboration; you don't know exactly what its Element
is, but you know it is a specific type. That means you can take elements out of c
and put them back in, but you wouldn't be able to do this:
without being able to express that the return type's Element == Int
.
You're right, the latter wouldn't be accepted, because there's nothing in the declaration to bind the underlying type from. I personally suspect that opaque types would be most useful with "function-like" get-only computed properties and subscripts, and that you're right that they wouldn't be terribly useful otherwise with more traditional storage-based properties.
This would work by giving vf1
its own opaque type.
Thanks!
Yeah.
It wouldn't be very useful to use a final class constraint since it's effectively a same-type constraint, but I don't think we ban that in general.
This is a temporary restriction; we could generalize to allow opaque types in structural positions in types in the future. I've updated the proposal to include this. Note that some Collection where Element == some P
would effectively be equivalent to some Collection
, though, since you're not revealing anything about the Element
to callers.
That's definitely a concern with introducing this feature. The evolution of traits and existentials in Rust is interesting to consider; like Swift, Rust originally gave trait existentials pride of place in the type grammar by making Trait
spell the existential type, but they quickly found that their version of opaque result types was a better solution to many library design problems, and they demoted trait existentials by making them take on a keyword.