简体   繁体   中英

Cast a Swift generic class to a protocol with a typealias

Am I crazy or shouldn't this swift code compile?

protocol Protocol {
  typealias Thing
}

class Class<X>: Protocol {
  typealias Thing = X
}

func test<X:Protocol where X.Thing == Int> () -> X {
  return Class<Int>()  // error: cannot convert return expression of type 'Class<Int>' to return type 'X'
}

I can't seem to cast the object to its protocol even though the generic type and aliastype match.

EDIT:

I came up with the code above by extracting the logic out of my existing code in an effort to simplify the problem. I made some mistakes in doing so. Here is an updated (and hopefully less confusing) code sample:

protocol Protocol {
    typealias Thing
}
class Class<X>: Protocol {
    typealias Thing = X
}
func test<Y: Protocol where Y.Thing == Int> () -> Y {
    return Class<Y.Thing>()
}

I expected the compiler to allow test() to compile with the result type being Protocol<Int> .

Your return type is impossible in today's Swift. A protocol with an associated type (PAT) is abstract. Applying a where clause doesn't change that. Consider this code:

let x: <WHAT-GOES-HERE?> = test()

What type would x be here? There is nothing you could write there that would compile. What would x.Type return? What you want it to be is Protocol where Protocol.Thing == Int , but that's not a type in Swift. It's a type constraint . That's the only way PATs can be used today. That's why you can't have a property of type CollectionType<Int> , and why you can't write your test() function.

The solution is a type-eraser to convert your protocol into a concrete struct. For example:

protocol Protocol {
    typealias Thing
    func dosomething() -> Thing?
}
class Class<X>: Protocol {
    typealias Thing = X
    func dosomething() -> Thing? {
        return nil
    }
}

struct AnyProtocol<Thing> {
    var _dosomething: () -> Thing?
    func dosomething() -> Thing? {
        return _dosomething()
    }
}

func test() -> AnyProtocol<Int> {
    return AnyProtocol(_dosomething: Class<Int>().dosomething)
}

It is possible that some future version of Swift would automatically generate these type erasers for you, but I'm not aware of any specific Swift-evolution proposal for one, so we have to write them by hand today.

For more on building and using type erasers, see A Little Respect for AnySequence .

Am I crazy or shouldn't this swift code compile?

You might be crazy, but I wouldn't use your code here as a test of that. Still, the code makes no sense, so the compiler is right to complain. It makes no sense to me either, so it's a little hard even for a human being to guess what you might mean, but perhaps you mean this:

protocol Protocol {
    typealias Thing
}
class Class<X>: Protocol {
    typealias Thing = X
}
func test<Y:Protocol where Y.Thing == Int> () -> Class<Y> {
    return Class<Y>()
}

But even though that compiles, I still don't see what you're getting at. For one thing, I don't see how the Y in test is going to be resolved by actual code. Maybe (judging here from the title) the problem is that you are expecting generic types to be castable to other generic types. They are not. For example, given class Animal and its subclass Dog, you cannot interchange between MyGeneric<Animal> and MyGeneric<Dog> .

Your comment also makes no sense:

what I'm trying to do is cast Class<Int> to Protocol<Int>

There is no such type as Protocol<Int> . There are only particular adopters of Protocol that specialize Protocol so that Thing is Int.

The reason it fails is simple: you're not respecting the specifics you yourself defined.

When you use generics, you have a generic type (in this case X ) which doesn't just mean "anything that satisfies the constraints", but "something specific that satisfies the constraints".

So, if you do something like let foo: Class<Int> = test() your code could still look alright. But then if you do something like

class AnotherClass<X>: Protocol {
  typealias Thing = X
}
let bar: AnotherClass<Int> = test()

you see clearly why the above is wrong.

The main concept here is, when you work with generics you can only use what the generic gives you. In this case, use only X and X.Thing , which is an Int . Nothing else, unless you add something to Protocol or other constraints in the function test

Update:

I see now with the updated question. Look at Rob Napier answer, what you want is a Type Eraser. Though I think that the talk in swift-evolution on existential types could bring this feature in?

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