Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Cast a Swift generic class to a protocol with a typealias

Tags:

generics

swift

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>.

like image 588
Sebastien Martin Avatar asked Mar 21 '16 01:03

Sebastien Martin


People also ask

How do I use Typealias in Swift?

A type alias allows you to provide a new name for an existing data type into your program. After a type alias is declared, the aliased name can be used instead of the existing type throughout the program. Type alias do not create new types. They simply provide a new name to an existing type.

How do I call a generic function in Swift?

Solution. A generic function that you might need to use explicit specialization is the one that infer its type from return type—the workaround for this by adding a parameter of type T as a way to inject type explicitly. In other words, we make it infer from method's parameters instead.

What is associated type IOS Swift?

What is an associated type? An associated type can be seen as a replacement of a specific type within a protocol definition. In other words: it's a placeholder name of a type to use until the protocol is adopted and the exact type is specified.


2 Answers

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.

like image 60
Rob Napier Avatar answered Sep 30 '22 04:09

Rob Napier


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.

like image 20
matt Avatar answered Sep 30 '22 04:09

matt