Search code examples
swiftgenericsassociated-types

Defining concrete associatedtype but I still can't use protocol as variable


I would like to use SendMessageUseCase as a variable type, for example var sendMessageUseCase: SendMessageUseCase but I can't.

I keep getting the error Protocol 'SendMessageUseCase' can only be used as a generic constraint because it has Self or associated type requirements

Which obviously I understand the error but it doesn't make sense.

In SendMessageUseCase I have defined my associated type using Where clause which means technically the compiler should be able to infer, since I have explicitly defined Input and Output to be SendMessageUseCaseInput and SendMessageUseCaseOutput respectively.

protocol UseCaseInput {}
protocol UseCaseOutput {}
protocol UseCase : AnyObject {
    associatedtype Input: UseCaseInput
    associatedtype Output: UseCaseOutput
    
    func execute(input: Input, _ completion: (_ output: Output) -> ())
}

struct SendMessageUseCaseInput : UseCaseInput {
    var text: String
}

struct SendMessageUseCaseOutput: UseCaseOutput {
    var response: String
}

protocol SendMessageUseCase: UseCase where Input == SendMessageUseCaseInput, Output == SendMessageUseCaseOutput {
    
}

final class TestSendMessageUseCase : SendMessageUseCase {
    func execute(input: SendMessageUseCaseInput, _ completion: () -> ()) {
        
    }
}

// Why this doesn't work?
var sendMessageUseCase: SendMessageUseCase = TestSendMessageUseCase()

can SendMessageUseCase.Input be anything else other than SendMessageUseCaseInput? I don't think so which is why I think it should be inferd. But obviously I am wrong and can't understand why.


Solution

  • Thanks to a comment from @Jessy I was able to come to a temporary solution.

    Adding some to the sendMessageUseCase variable would allow us to use opaque type but there is still a limitation as you can not have an array.

    There are some talks to implement this but as of now (May, 28 2022) it still hasn't been implemented.

    protocol UseCaseInput {}
    protocol UseCaseOutput {}
    protocol UseCase : AnyObject {
        associatedtype Input: UseCaseInput
        associatedtype Output: UseCaseOutput
        
        func execute(input: Input, _ completion: (_ output: Output) -> ())
    }
    
    struct SendMessageUseCaseInput : UseCaseInput {
        var text: String
    }
    
    struct SendMessageUseCaseOutput: UseCaseOutput {
        var response: String
    }
    
    protocol SendMessageUseCase: UseCase where Input == SendMessageUseCaseInput, Output == SendMessageUseCaseOutput {
    }
    
    final class TestSendMessageUseCase : SendMessageUseCase {
        func execute(input: SendMessageUseCaseInput, _ completion: (SendMessageUseCaseOutput) -> ()) {
            
        }
        
    }
    
    var sendMessageUseCase: some SendMessageUseCase = TestSendMessageUseCase()
    

    If anyone is interested here are the forum threads

    Lifting the “Self or associated type” constraint on existentials

    [ [Sema]AST][WIP] Support existentials with concrete assoc. types #21576 (PR is closed)