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.