it’s reasonable to assume that when @stuchlej asked about
public struct Container<T: Encodable> {
public var value: T
public func bytes(_ val: Int) { print("Int") }
public func bytes(_ val: String) { print("String") }
public func bytes<A: Encodable>(_ val: A) { print("A") }
public func store() {
bytes(value)
}
}
he was asking why the generics weren’t inlined into
public struct _Container_Int {
public var value: Int
public func bytes(_ val: Int) { print("Int") }
public func bytes(_ val: String) { print("String") }
public func bytes<A: Encodable>(_ val: A) { print("A") }
public func store() {
bytes(value)
}
}
before type checking, which would allow the call to bytes(_:)
inside store
to resolve to the one that prints "Int"
. the call to store
would never have any resilience overhead, because it just calls the (Int) -> ()
overload.
you are completely correct that there are lots of downsides to inlining generics before typechecking, probably more than the amount of upsides to it, but one of those upsides is that using _Container_Int
never incurs resilience overhead.
i personally think that swift made the right choice in typechecking generics instead of specializations, and that inlining generics before typechecking was never going to scale. but my point this entire time has been that inlining after typechecking doesn’t work very well today, because we don’t have tools in the language today to statically assert that @inlinable
has been correctly applied to all participants in a call chain that clients who do not care about resilience might call.
let’s walk through how protocol-based dispatch might work for Container<Int>
.
protocol HasBytesTypeName
{
static
var name:String { get }
}
extension HasBytesTypeName where Self:Encodable
{
public static
var name:String { "A" }
}
extension Int:HasBytesTypeName
{
public static
var name:String { "Int" }
}
then we would refactor Container<T>
into something like:
public
struct Container<T> where T:HasBytesTypeName & Encodable
{
public
var value:T
public
func bytes<Value>(_ value:Value)
where Value:HasBytesTypeName & Encodable
{
print(Value.name)
}
public
func store()
{
self.bytes(self.value)
}
}
now, how would we use such a type?
import ContainerModule
let container:Container<Int> = ...
container.store()
in the absence of @inlinable
, this call to store
would call the unspecialized implementation, because Container
doesn’t know what types Container<T>
it might be asked to construct. this is going to be slow.
sometimes we do not care, because we do not consult type metadata in the implementation, or we do consult type metadata but the implementation does so many other things that the overhead of the generics is unimportant. but oftentimes we do care, and it would be helpful if the type system could help with things like diagnosing missing @inlinable
s.