简体   繁体   中英

Use Type Erasure return Generic Type in a function with Swift (Cannot convert return expression of type…)

I have a problem with generics in swift. Let's expose my code.


protocol FooProtocol {
    associatedtype T
}

protocol Fooable { }
extension Int : Fooable { }
extension String: Fooable { }

class AnyFoo<T>: FooProtocol {
    init<P: FooProtocol>(p: P) where P.T == T { }
}

class FooIntImpClass: FooProtocol {
    typealias T = Int
}

class FooStringImpClass: FooProtocol {
    typealias T = String
}

func createOne(isInt: Bool) -> AnyFoo<Fooable> {
    if isInt {
        let anyFoo = AnyFoo(p: FooIntImpClass())
          return anyFoo
    } else {
        let anyFoo = AnyFoo(p: FooStringImpClass())
        return anyFoo
    }
}

func createTwo<F: Fooable>(isInt: Bool) -> AnyFoo<F> {
    if isInt {
        let anyFoo = AnyFoo(p: FooIntImpClass())
          return anyFoo
    } else {
        let anyFoo = AnyFoo(p: FooStringImpClass())
        return anyFoo
    }
}

createOne got an error

Cannot convert return expression of type 'AnyFoo' (aka 'AnyFoo') to return type 'AnyFoo'

createTwo got an error

Cannot convert return expression of type 'AnyFoo' (aka 'AnyFoo') to return type 'AnyFoo'

Why is this happening. I'm returning the correct value.

And What is the difference with the createOne and createTwo

EDIT to respond to the edit to the question:

createTwo doesn't work because you have the same misconception as I said in my original answer. createTwo decided on its own that F should be either String or Int , rather than "any type that conforms to Fooable ".

For createOne , you have another common misconception. Generic classes are invariant . AnyFoo<String> is not a kind of AnyFoo<Fooable> . In fact, they are totally unrelated types! See here for more details.

Basically, what you are trying to do violates type safety, and you redesign your APIs and pick another different approach.


Original answer (for initial revision of question)

You seem to be having a common misconception of generics. Generic parameters are decided by the caller, not the callee.

In createOne , you are returning anyFoo , which is of type AnyFoo<Int> , not AnyFoo<P> . The method (callee) have decided, on its own, that P should be Int . This shouldn't happen, because the caller decides what generic parameters should be. If the callee is generic, it must be able to work with any type (within constraints). Anyway, P can't be Int here anyway, since P: FooProtocol .

Your createOne method should not be generic at all, as it only works with Int :

func createOne() -> AnyFoo<Int> {
    let anyFoo = AnyFoo(p: FooImpClass())
    return anyFoo
}

Is the following what you tried to achieve? (compiled & tested with Xcode 11.4)

func createOne() -> some FooProtocol {
    let anyFoo = AnyFoo(p: FooImpClass())
    return anyFoo
}

EDIT I finally managed to keep your where clause :)

EDIT Still not sure what you want to do, and I still agree with @Sweeper but I love to badly abuse generics:):


protocol FooProtocol {
    associatedtype T
    init()
}

protocol Fooable { }
extension Int : Fooable { }
extension String: Fooable { }

class AnyFoo<T>: FooProtocol {
    init<P: FooProtocol>(p: P) where P.T == T { }
    init<T>(p: T.Type) { }
    required init() { }
}

class FooIntImpClass: FooProtocol {
    typealias T = Int
    required init() { }
}

class FooStringImpClass: FooProtocol {
    typealias T = String
    required init() { }
}


func createOne<F: FooProtocol>(foo: F.Type) -> AnyFoo<F.T> {
    let anyFoo = AnyFoo<F.T>(p: F.init())
        return anyFoo
}

func createTwo<F: FooProtocol>(foo: F.Type) -> some FooProtocol {
    let anyFoo = AnyFoo<F.T>(pk: F.T.self)
    return anyFoo
}

that compiles but I don't know what to do with it.

Edit

yeah I really don't know:

let one = createOne(foo: FooStringImpClass.self) // AnyFoo<String>
print(type(of: one).T) // "String\n"
let two = createTwo(foo: FooIntImpClass.self) // AnyFoo<Int>
print(type(of: two).T) // "Int\n"

Is that what you wanted?


I'm not sure what you want to do with that, but I suggest you try putting a where clause on the AnyFoo class instead of it's initializer. And I should add, that your where clause on the initializer was wrong, Like Sweeper said:

Anyway, P can't be Int here anyway, since P: FooProtocol.

The following code compiles:

 protocol FooProtocol { associatedtype T } class AnyFoo<T>: FooProtocol where T: FooProtocol { init<P: FooProtocol>(p: P) { } } class FooImpClass: FooProtocol { typealias T = Int } func createOne<P: FooProtocol>() -> AnyFoo<P> { let anyFoo: AnyFoo<P> = AnyFoo(p: FooImpClass()) return anyFoo }

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM