newtype
is most closely related to opaque type aliases, because both introduce a new named type that is primarily described by its capabilities but has the same storage as some underlying type. One can think of new type
as sugar for a struct containing a single stored property of the underlying type, so something like:
newtype Meters = Int
would desugar to something like:
struct Meters {
var underlying: Int
}
Generally, one would expect some kind of opt-in forwarding of APIs from the underlying type to the newtype
. For example:
newtype Meters: Numeric = Int
Now, Meters
should conform to Numeric
, using the implementations from the conformance of Int
to Numeric
. But this starts to fall apart when Self
is used in the the protocols. For example:
protocol RandomValueSource {
func getRandomValues(_ numValues: Int) -> [Self]
}
extension Int: RandomValueSource {
func getRandomValues(_ numValues: Int) -> [Int] {
return Array((0..<numValues).map(_ in Int.random(in: 0..<100)))
}
}
How do we forward that conformance of RandomValueSource
for Meters
? It returns an [Int]
, and now we need a [Meters]
, even though it is a completely different type.
Because of this, I've mostly lost interest in newtype
. Maybe Swift should get some kind of forwarding syntax to make it easy to write something like Meters
as a wrapper type for Int
, with opt-in synthesis for all of the members where it does work. That would be a nice way to eliminate some boilerplate from the system without introducing a new concept.
Opaque result types don't have these issues because there is no new runtime type involved.
Doug