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