Search code examples
swiftprotocolsassociated-types

Mysterious error caused by reference to invalid associated type in protocol


An online Swift 5.1 compiler compiles the following code and reports errors. The most important one is stated as follows:

main.swift:18:23: error: reference to invalid associated type 'Other' of type 'Sparrow'
func sing(with f: Other) {
                  ^

Another thread on Stackoverflow brought up the same issue with a more complicated case. (Reference to invalid associated type 'Iterator' of type 'DecodedArray<T>'). There have been no answers to that thread yet.

The code for my case and a full list of compiler-reported errors is shown below:

    protocol Flier {
    associatedtype Other

    func flockTogether(with f:Other)
    func sing(with f:Other)
}

struct Sparrow : Flier {
    let name: String

    init (_ name: String) {
        self.name = name
    }

    func flockTogether(with f: Other) {
        print("Birds, \(name), and \(f.name), of a feather flock together.")
    }
    func sing(with f: Other) {
        sing()
        f.sing()
    }
    func sing () {
        print("Sparrow sings \"chirp, chirp\"!")
    }
}

struct Parrot {
    let name: String

    init (_ name: String) {
        self.name = name
    }

    func sing () {
        print("Parrot sings \"chuck, chuck\"!")
    }
}
struct Canary: Flier {
    let name: String
        
    init (_ name: String) {
        self.name = name
    }
    func flockTogether(with f: Other) {}
    func sing(with f: Other) {}

    func sing () {
        print("Canary sings \"tweet, tweet\"!")
    }
}
let sparrow = Sparrow("sparrow")
let parrot = Parrot("parrot")
let canary = Canary("canary")
sparrow.flockTogether(with: parrot)
sparrow.sing(with: parrot)
sparrow.flockTogether(with: canary)
sparrow.sing(with: canary)

The errors reported by the compiler:

main.swift:18:23: error: reference to invalid associated type 'Other' of type 'Sparrow'
    func sing(with f: Other) {
                      ^
main.swift:8:8: error: type 'Sparrow' does not conform to protocol 'Flier'
struct Sparrow : Flier {
       ^
main.swift:2:20: note: protocol requires nested type 'Other'; do you want to add it?
    associatedtype Other
                   ^
main.swift:38:8: error: type 'Canary' does not conform to protocol 'Flier'
struct Canary: Flier {
       ^
main.swift:2:20: note: protocol requires nested type 'Other'; do you want to add it?
    associatedtype Other
                   ^
compiler exit status 1

Please help me find out what has gone wrong in the above code. Thank you very much!


Solution

  • There are quite a few things wrong with this code.

    1. Sparrow and Canary are declared to conform to Flier yet doesn't say what their respective Other type is.

    2. You are trying to pass both a parrot and a canary to sparrow.flockTogether(with:) and sparrow.sing(with:), but those methods only accept one type of object - Sparrow.Other. This, and the above point, suggest that you might be misunderstanding what associated types are. I suggest you read about them.

    3. You are trying to access things that doesn't necessarily exist, such as f.name and f.sing(). Recall that f is an Other, which is not constrained to any type, so it can be anything. And "anything" won't always have a name for you to access.

    I suggest the following to make the callers work:

    1. Remove the associated type and use generic methods instead. Associated types are inappropriate if the caller gets to decide whether to pass in a Parrot or Canary.

    2. Add name and sing() to Flier so that the compiler knows that anything conforming to Flier has those members. If we then constrain Other (the generic parameter of the aforementioned generic methods) to Flier, then we can access sing() and name without any problems.

    3. Conform Parrot to Flier as well

    The fixed code now looks like:

    protocol Flier {
        var name: String { get }
        func flockTogether<Other: Flier>(with f:Other)
        func sing<Other: Flier>(with f:Other)
        func sing()
    }
    
    struct Sparrow : Flier {
        let name: String
    
        init (_ name: String) {
            self.name = name
        }
    
        func flockTogether<Other: Flier>(with f:Other) {
            print("Birds, \(name), and \(f.name), of a feather flock together.")
        }
        func sing<Other: Flier>(with f:Other) {
            sing()
            f.sing()
        }
        func sing () {
            print("Sparrow sings \"chirp, chirp\"!")
        }
    }
    
    struct Parrot : Flier{
        func flockTogether<Other>(with f: Other) where Other : Flier { }
        
        func sing<Other>(with f: Other) where Other : Flier { }
        
        let name: String
    
        init (_ name: String) {
            self.name = name
        }
    
        func sing () {
            print("Parrot sings \"chuck, chuck\"!")
        }
    }
    struct Canary: Flier {
        let name: String
    
        init (_ name: String) {
            self.name = name
        }
        func flockTogether<Other: Flier>(with f:Other) {}
        func sing<Other: Flier>(with f:Other) {}
    
        func sing () {
            print("Canary sings \"tweet, tweet\"!")
        }
    }