There is a protocol Printable and a struct Printer from a 3rd Party.
protocol Printable {}
struct Printer {
static func print<T>(object: T) -> String {
return "T"
}
static func print<T: Printable>(object: T) -> String {
return "Printable"
}
}
Now i am making a generic
struct Generic<T> {
var args: T
func display() {
print(Printer.print(args))
}
}
and two structs
struct Obj {}
struct PrintableObj: Printable {}
var obj = Generic(args: Obj())
var printableObj = Generic(args: PrintableObj())
When i call the display functions on both of them.
obj.display()
displays T
printableObj.display()
displays T but i want it to print "Printable"
One solution i can think of is having two different generics
struct Generic<T>
struct PrintableGeneric<T: Printable>
Is there any other solution without changing the Printable protocol and Printer struct.
static func print<T>(object: T) -> String {
if object is Printable {
return "Printable"
} else {
return "T"
}
}
Yes. But the answer is a bit weird. The first part makes a decent amount of sense; the second part is just totally weird. Let's walk through it.
struct Generic<T> {
var args: T
func display() {
print(Printer.print(args))
}
}
The correct overload to choose for print
is decided at compile time, not runtime. This is the thing that confuses people the most. They want to treat Swift like JavaScript where everything is dynamic. Swift likes to be static because then it can make sure your types are right and it can do lots of optimizations (and Swift loves to do compiler optimizations). So, compile time, what type is args
? Well, it's T
. Is T
known to be Printable
? No it is not. So it uses the non-Printable
version.
But when Swift specializes Generic
using PrintableObj
, doesn't it know at that point that it's Printable
? Couldn't the compiler create a different version of display
at that point? Yes, if we knew at compile time every caller that would ever exist of this function, and that none of them would ever be extended to be Printable
(which could happen in a completely different module). It's hard to solve this without creating lots of weird corner cases (where internal things behave differently than public things for instance), and without forcing Swift to proactively generate every possible version of display
that might be required by some future caller. Swift may improve in time, but this is a hard problem I think. (Swift already suffers some performance reductions so that public generics can be specialized without access to the original source code. This would make that problem even more complicated.)
OK, so we get that. T
isn't Printable
. But what if we had a type that was unambiguously Printable
that we knew at compile time and lived inside this function? Would it work then?
func display() {
if let p = args as? Printable {
print(Printer.print(p))
} else {
print(Printer.print(args))
}
}
Oh so close... but not quite. This almost works. The if-let
actually does exactly what you want it to do. p
gets assigned. It's Printable
. But it still calls the non-Printable function. ?!?!?!?!
This is a place I personally think that Swift is just currently broken and have high hopes it will be fixed. It might even be a bug. The problem is that Printable
itself does not conform to Printable
. Yeah, I don't get it either, but there you go. So we need to make something that does conform to Printable
in order to get the right overload. As usual, type erasers to the rescue.
struct AnyPrintable: Printable {
let value: Printable
}
struct Generic<T> {
var args: T
func display() {
if let p = args as? Printable {
print(Printer.print(AnyPrintable(value: p)))
} else {
print(Printer.print(args))
}
}
}
And this will print the way you wanted. (On the assumption that Printable
requires some methods, you'd just add those methods to the AnyPrintable
type eraser.)
Of course the right answer is not to use generic overloads this way in Printer
. It's just way too confusing and fragile. It looks so nice, but it blows up all the time.
To my mind, the only option you have - is to use if-else with type casting in "print()" function
static func print<T>(object: T) -> String {
if let _ = object as? Printable {
return "Printable"
}
return "T"
}
or non-generic variant
static func print(object: Any) -> String {
if let _ = object as? Printable {
return "Printable"
}
return "T"
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With