Protocol as a type cannot conform to the protocol itself

I'm writing a generic navigator for SwiftUI views

protocol FlowNavigator {
    associatedtype FlowRoute
    func navigate<T: View>(_ route: FlowRoute, content: () -> T) -> AnyView
}

to use it with an enum representing current app flow

enum AppFlow {
    case access, createAccount, login, loginAndSecurity
    
    func getNavigator<T: FlowNavigator>() -> T? {
        switch self {
        case .access:
            return nil
        case .createAccount:
            return CreateAccountNavigator() as? T
        case .login:
            return nil
        case .loginAndSecurity:
            return LoginAndSecurityNavigator() as? T
        }
    }
}

with FlowNavigator implementation like this

class LoginAndSecurityNavigator: FlowNavigator {
    func navigate<T: View>(_ route: LoginAndSecurityRoute, content: () -> T) -> AnyView {
        switch route {
        case .changePassword:
            return NavigationLink(
                destination: Text("CHANGE PASSWORD")) {
                    content()
                }.toAnyView()
            
        case .linkedSocials:
            return NavigationLink(
                destination: LinkedSocialsScreen()) {
                    content()
                }.toAnyView()
        }
    }
}

where LoginAndSecurityRoute is a basic enum

enum LoginAndSecurityRoute {
    case changePassword
    case linkedSocials
}

(CreateAccountNavigator is basically the same, it differs only for navigationLinks)

Xcode doesn't complain with this code but when I try to use it with

let flow = AppState.AppFlow.loginAndSecurity
let flowNavigator: FlowNavigator? = flow.getNavigator()

compiler says

Protocol 'FlowNavigator' as a type cannot conform to the protocol
itself

I'm pretty sure solution is trivial but I cannot find a way to make it work...

@hborla gave an explanation of this error and explained how it motivates the adoption of any P syntax: SE-0335: Introduce existential `any` - #112 by hborla

The root of your problem is the associated type. The type of the value on the left hand side has no constraint on the associated type, so any code that uses it has no idea what the type of the first argument of navigate(:,content:) is.

2 Likes

filip-sakel shared this in AnyCodable Efficacy. To paraphrase: the existential box is opened on protocol methods, so by extending/trampolining off the protocol we can get access to the real type again. Genius!

Here's a worked example.

protocol Stuffable { }
struct Stuffer: Stuffable { }

func withStuff<T>(_ stuff: T) where T: Stuffable {
    print("Hello \(T.self)")
}

extension Stuffable {
    func trampoline() { withStuff(self) }
}

let value: Stuffable = Stuffer()
withStuff(value)      ❌
value.trampoline()    ✅
5 Likes