mezo
mezo

Reputation: 453

Calling non generic function from generic context swift

I'm having issues with understanding how matching types in generic functions work in swift. I do not understand why my code is not compiling.

This is what I have:

enum LoadingState<T> {
    case idle
    case loading
    case failed(Error)
    case loaded(T)
}

private func updateInternal(_ state: Int) {
    print("=int")
}

private func updateInternal(_ state: String) {
    print("=string")
}

private func update<T>(_ state: LoadingState<T>) {
    switch state {
    case .loaded(let viewModel):
        updateInternal(viewModel)
    default:
        break
    }
}

let stateInt: LoadingState<Int> = .loaded(42)
let stateString: LoadingState<String> = .loaded(String("Hello"))

update(stateInt)
update(stateString)

I'm getting following compilation error:

error: no exact matches in call to global function 'updateInternal'

However if I add generic function for updateInternal I'm able to compile the code:

private func updateInternal<T>(_ state: T) {
    print("=generic")
}

but when running the code only this generic function is matched in all calling scenarios. I was able to resolve this issue with this:

private func updateInternal<T>(_ state: T, type: T.Type) {
    if type == Int.self {
        updateInternal(state as! Int)
    }
    else if type == String.self {
        updateInternal(state as! String)
    }
}

private func update<T>(_ state: LoadingState<T>) {
    switch state {
    case .loaded(let viewModel):
        updateInternal(viewModel, type: T.self)
    default:
        break
    }
}

but I'm quite sure that this should not be necessary right? My question is therefore how to resolve this issue with calling non generic functions from generic context as specialized functions

Upvotes: 1

Views: 149

Answers (1)

idmean
idmean

Reputation: 14895

Your code looks an awful lot like you are used to C++ template programing (and looking at your profile seems to confirm my assumption). But Swift generics are not templates!

In this function

private func update<T>(_ state: LoadingState<T>) {
    switch state {
    case .loaded(let viewModel):
        updateInternal(viewModel)
    default:
        break
    }
}

The compiler will compile (or at least type check) this function exactly one time, unlike C++ templates.

It will check, whether it can call updateInternal with a value of generic type T.

How can it check that? It will look at the constraints of T and try to reduce T to the most specific type that is also available outside of your function. Since you have not constrained T to any type, the compiler only knows that all values of T will be covariant (compatible to) Any. But you don't provide any overload of updateInternal that accepts Any.

If you provide

private func updateInternal<T>(_ state: T) {
    print("=generic")
}

This can be called, of course, since it accepts literally any type (or, speaking differently, accepts Any).

If you provided

private func updateInternal(_ state: Any) {
    print("=any")
}

instead, your code will compile too.

but when running the code only this generic function is matched in all calling scenarios

Yeah, that's true. But the semantics of Swift don't allow the compiler to prove or use that fact. (Unlike in C++, where each template instantiation causes the compiler to separately compile the template with the provided types.)

My question is therefore how to resolve this issue with calling non generic functions from generic context as specialized functions

Swift doesn't have specialized functions because, once again, the compiler compiles the generic function only once. There's no way the compiler could select the right specialization. The same thing is achieved in Swift via object-orientation/dynamic dispatch (virtual in C++).

You can provide constraints on the type that can be passed as generic parameter. For example, this would work:

private func update<T: String>(_ state: LoadingState<T>) {
    switch state {
    case .loaded(let viewModel):
        updateInternal(viewModel)
    default:
        break
    }
}

Because now the compiler knows that T will be covariant with String and it can call updateInternal. But it is, of course, pointless.

You would normally define a protocol that embodies all the functionality of that T that you need (like providing and updateInternal method!) and constrain T to it.

I suggest you learn more about Type Constraints and Protocols.

Upvotes: 2

Related Questions