Overloading generic function on optional parameter in Swift

264 Views Asked by At

I'm trying to write a library that uses templated/generic type dispatch, but I can't figure out how overload resolution works in Swift. (Is there a more technical reference than The Swift Programming Language?)

The following works exactly as I'd hope:

func f(_ v: String) { print(v) }
func f(_ v: String?) { f(v!) }
func f(_ v: Int) { print(v) }
func f(_ v: Int?) { f(v!) }
f("foo")
f(Optional("bar"))
f(2)
f(Optional(3))

And this code also works the same way:

func g<T>(_ v: T) { print(v) }
func g<T>(_ v: T?) { g(v!) }
g("foo")
g(Optional("bar"))
g(2)
g(Optional(3))

But when I compile this:

func h(_ v: String) { print(v) }
func h(_ v: Int) { print(v) }
func h<T>(_ v: T?) { h(v!) }
h("foo")
h(Optional("bar"))
h(2)
h(Optional(3))

I get the warning

all paths through this function will call itself
func h<T>(_ v: T?) { h(v!) }

And sure enough executing it with an optional parameter blows the stack.

My first zany theory was that maybe generics don't participate in overload resolution with non-generics, but this:

func i<T: StringProtocol>(_ v: T) { print(v) }
func i<T>(_ v: T?) { i(v!) }
i("foo")
i(Optional("bar"))

gives me the same warning, and the second invocation blows the stack.

If v is of type Optional(String), I don't even understand how v! can be passed to a generic expecting a T?.

2

There are 2 best solutions below

2
On

This is happening because in your generic implementation there won't be any type inference happening. You could solve this by doing some optional casting (to avoid crashes) and sending it to the correct method, which will actually happen automatically anyway. So something like:

func h<T>(_ v: T?) { 
  if let stringValue = v as? String {
    h(stringValue) 
  } else if let intValue = v as? Int {
    h(intValue) 
  }
}

Maybe kinda defeats the purpose, but I can tell you as a Swift developer with several years of experience this type of generic handling doesn't/shouldn't really come up all that often in a real application if the rest of your code is written in a Swift-friendly way. I suppose that's a bit opinion-based, but there it is.

As for this comment:

If v is of type Optional(String), I don't even understand how v! can be passed to a generic expecting a T?.

Based on your implementation you are declaring that v: T?, which means that v must be either of type T or it is nil. Therefore v! is just you (as a developer) guaranteeing that v will be of type T, and not nil, and if you're wrong the program will crash.

I assume you are doing this just for learning purposes, but that is the biggest thing I notice in your example code - there would never be any point of having a method accept an Optional argument if you are going to immediately use ! to force unwrap.

1
On

I'd love a more direct answer to how generics work, but Swift's protocols and extensions provide a different approach to the same kind of dispatch.

protocol H { func h() }
extension String: H { func h() { print(self) } }
extension Int: H { func h() { print(self) } }
extension Optional: H where Wrapped: H { func h() { self!.h() } }
"foo".h()
Optional("bar").h()
2.h()
Optional(3).h()
Optional(Optional(3)).h()

Unfortunately, this dispatch mechanism cannot, as far as I know, be applied to tuples (or function types), as they cannot be extended.