Why generics don't compile?

For starters, let's explain the error:

Given:

class Foo<T:Hashable> { }

class SubFoo<String> : Foo<String> { }

The confusing part here is that we expect "String" to mean the Swift defined structure which holds a collection of characters. But it's not.

Here, "String" is the name of the generic type we've given our new subclass, SubFoo. This becomes very obvious if we make some changes:

class SubFoo<String> : Foo<T> { }

This line generates an error for T as the use of an undeclared type.

Then if we change the line to this:

class SubFoo<T> : Foo<T> { }

We're back to the same error you originally had, 'T' does not conform to 'Hashable'. It's obvious here, because T isn't confusingly the name of an existing Swift type that happens to conform to 'Hashable'. It's obvious 'T' is a generic.

When we write 'String', it's also just the placeholder name for a generic type, and not actually the String type that exists in Swift.


If we want a different name for a specific type of a generic class, the appropriate approach is almost certainly a typealias:

class Foo<T:Hashable> {

}

typealias StringFoo = Foo<String>

This is perfectly valid Swift, and it compiles just fine.


If instead what we want is to actually subclass and add methods or properties to a generic class, then what we need is a class or protocol that will make our generic more specific to what we need.

Going back to the original problem, let's first get rid of the error:

class Foo<T: Hashable>

class SubFoo<T: Hashable> : Foo<T> { }

This is perfectly valid Swift. But it may not be particularly useful for what we're doing.

The only reason we can't do the following:

class SubFoo<T: String> : Foo<T> { }

is simply because String is not a Swift class--it's a structure. And this isn't allowed for any structure.


If we write a new protocol that inherits from Hashable, we can use that:

protocol MyProtocol : Hashable { }

class Foo<T: Hashable> { }
class SubFoo<T: MyProtocol> : Foo<T> { }

This is perfectly valid.

Also, note that we don't actually have to inherit from Hashable:

protocol MyProtocol { }
class Foo<T: Hashable> { }
class SubFoo<T: Hashable, MyProtocol> { }

This is also perfectly valid.

However, note that, for whatever reason, Swift won't let you use a class here. For example:

class MyClass : Hashable { }

class Foo<T: Hashable> { }
class SubFoo<T: MyClass> : Foo<T> { }

Swift mysteriously complains that 'T' does not conform to 'Hashable' (even when we add the necessary code to make it so.


In the end, the right approach, and the most Swift-appropriate approach is going to be writing a new protocol that inherits from 'Hashable' and adds whatever functionality to it you need.

It shouldn't be strictly important that our subclass accept a String. It should be important that whatever our subclass takes, it has the necessary methods and properties that we need for whatever we're doing.


I'm not a Swift developer, but having seen similar problems in Java, I suspect the problem is that at the moment you're declaring a type parameter called String because you're declaring class FooTranslator<String> - so the type argument in Translator<String> is just that type parameter, which has no constraints. You don't want a type parameter at all, I suspect (i.e. you don't want your FooTranslator to be a generic class itself.)

As noted in comments, in Swift subclasses of a generic class also have to be generic. You could possibly declare a throw-away type parameter, like this:

class FooTranslator<T>:Translator<String>

which still avoids declaring a new type parameter called String, which was what was causing the problem. It means you're introducing a new type parameter when you don't want any type parameters, but it's possibly better than nothing...

This is all on the assumption that you really need a subclass, e.g. to add or override members. On the other hand, if you just want a type which is exactly the same as Translator<String>, you should use a type alias instead:

typealias FooTranslator = Translator<String>

Or even mix the two in a horrible way, if you genuinely want a subclass but don't want to have to refer to it in a generic way:

class GenericFooTranslator<T>:Translator<String>
typealias FooTranslator = GenericFooTranslator<Int>

(Note that the Int here is deliberately not String, to show that the T in Translator isn't the same as the T in FooTranslator.)

Tags:

Generics

Swift