Search code examples
swiftgenericsassociated-types

Swift: Question on Generic Functions and Associated Types


Say, I have a protocol with an associated type:

protocol Model {
    associatedtype ModelType
}

And there's a generic struct conforming to this protocol:

struct Implementation<T>: Model {
    typealias ModelType = T
}

Now, I want to add an instance method to this struct accepting and returning a value of the abstract Model type:

struct Implementation<T>: Model {
    typealias ModelType = T
    func test<M: Model>(model: M) -> M {
        return model
    }
}

This compiles without errors. I want to go further and make sure that both the model parameter and the return value of the test(model:) method have the same associated ModelType as the Implementation instance this method is called on:

struct Implementation<T>: Model {
    typealias ModelType = T
    func test<M: Model>(model: M) -> M where M.ModelType == T {
        return model
    }
}

This compiles without errors, and it works as expected. If there's a second type conforming to Model

struct Alternative<T>: Model {
    typealias ModelType = T
}

…, I can pass an instance of this type to the test(model:) method only if it has the same associated ModelType. That is, the following code compiles…

let implementation = Implementation<Int>()
let alternativeA = Alternative<Int>()
_ = implementation.test(model: alternativeA)

…, whereas the following code:

let implementation = Implementation<Int>()
let alternativeB = Alternative<String>()
_ = implementation.test(model: alternativeB)

… fails to compile with an error saying,

Instance method 'test(model:)' requires the types 'Int' and 'Alternative<String>.ModelType' (aka 'String') be equivalent

Now, under some condition, the test(model:) method should return self. If I change it to (always) return self, however…

struct Implementation<T>: Model {
    typealias ModelType = T
    func test<M: Model>(model: M) -> M where M.ModelType == T {
        return self
    }
}

…, I'll get an error saying,

Cannot convert return expression of type 'Implementation<T>' to return type 'M'

This is confusing, as, clearly, the type of self should match all given constraints. (Or doesn't it?)

The error is, in fact, unrelated to the where clause. If I drop it…

struct Implementation<T>: Model {
    typealias ModelType = T
    func test<M: Model>(model: M) -> M {
        return self
    }
}

…, the error will persist:

Cannot convert return expression of type 'Implementation<T>' to return type 'M'

Why am I getting this error, and how can I return self from this method?

(I'm using Swift 5.3.2 by the way in case this matters.)


Solution

  • You are getting this error, because indeed self does not match all given constraints. By stating func test<M: Model>(model: M) -> M where M.ModelType == T you only say that this type M (which is only exists in the context of a call to your test function) is to be a Model, which's ModelType is the same as the ModelType of self. That does not mean, however, that Self and M are equivalent.

    A little example using your types from above:

    Assuming self is an instance of Implementation<Int>. In the context of a call to test(model:), M could be either of Implementation<Int> or Alternative<Int>. Returning self wouldn't be a problem in the first case, as both instances have the same type. However, in the second case you could assign an Implementation<Int> to an Alternative<Int>.

    var a = Alternative<Int>()
    let x = Implementation<Int>()
    // This would assign something of type `Implementation<Int>` to a variable that
    // may only contain `Alternative<Int>`.
    var a = x.test(model: a)
    

    I am sure there is a method to do what you want to achieve in Swift, but the solution totally depends on what that is.