Primary associated type protocol error when conforming on a struct (but not with class)

Hello :wave:

I have been playing around with generics in Swift5.7 and ran into an error where I can't figure out the reason behind it.
The same protocol (Protocol2 in the example below) can be conformed to by a class but not by a struct.
I might be missing something very basic here. Happy for any suggestions or insights! :pray:

import Combine

protocol AnimalType: Hashable {
    var name: String { get }
}

struct Chicken: AnimalType {
    let name: String
}

// Protocols with primary associated types and "simple" function

protocol Protocol1<Animal> {
    associatedtype Animal: AnimalType
    func test(set: Set<Animal>)
}

// A struct conforming to this protocol without issues

struct TestStruct1: Protocol1 {
    func test(set: Set<Chicken>) {
        Set<Chicken>()
    }
}

// A second protocol with a nested(?) generic combine publisher
protocol Protocol2<Animal> {
    associatedtype Animal: AnimalType
    var publisher: AnyPublisher<Set<Animal>, Never> { get }
}

// A struct that's not able to conform to this protocol
// * "Type 'TestStruct' does not conform to protocol 'Protocol2'"
// * Xcode's auto-fix is asking for a typealias for the generic type `typealias Animal = type`
struct TestStruct2: Protocol2 {
    private(set) lazy var publisher = subject.eraseToAnyPublisher()
    private var subject = PassthroughSubject<Set<Chicken>, Never>()
}

// Same implementation as above, expect that here we're using a class and it's working without any issues
class TestClass2: Protocol2 {
    private(set) lazy var publisher = subject.eraseToAnyPublisher()
    private var subject = PassthroughSubject<Set<Chicken>, Never>()
}

I suspect it is caused by the lazy type in struct.

If we change to use a normal getter or just get ride of the lazy type, it will work normally.

// A struct that's not able to conform to this protocol
// * "Type 'TestStruct3' does not conform to protocol 'Protocol2'"
// * Xcode's auto-fix is asking for a typealias for the generic type `typealias Animal = type`
struct TestStruct3: Protocol2 {
    // At first, I thought it has something to do with the wrong type inference
    // Actually it was the same as private(set) lazy var publisher = subject.eraseToAnyPublisher()
    // You can confirm it by right click pulisher to get the type inference
    private(set) lazy var publisher: AnyPublisher<Set<Chicken>, Never> = subject.eraseToAnyPublisher()
    private var subject = PassthroughSubject<Set<Chicken>, Never>()
}

// Almost the same implementation, it's working without any issues
struct TestStruct4: Protocol2 {
    var publisher: AnyPublisher<Set<Chicken>, Never> {
        subject.eraseToAnyPublisher()
    }
    private var subject = PassthroughSubject<Set<Chicken>, Never>()
}

// Almost the same implementation, it's working without any issues
struct TestStruct5: Protocol2 {
    private(set) var publisher = PassthroughSubject<Set<Chicken>, Never>().eraseToAnyPublisher()
}
2 Likes

Another solution is to indicate on the protocol that the getter can mutate the thingie (like the lazy struct properties do)

    var publisher: AnyPublisher<Set<Animal>, Never> { mutating get }

It will prevent you from using publisher property on let variables

1 Like

Thank you so much for your input @Kyle-Ye and @cukr !

I understand the issue now. struct being an immutable value type and lazy mutating it after initiation. Thank you for the suggestions of mutating get and simply removing the lazy implementation. :pray: