I'm trying to implement following structure using generics. Getting a compiler error, can't figure out why.
class Translator<T:Hashable> {...} class FooTranslator<String>:Translator<String> {...}
The idea is that Translator uses T as the type of a key in a dictionary. This can be e.g. a String or an enum. Subclass provides concrete dictionary.
But it fails because: "Type 'String' does not conform to protocol 'Hashable'"
But String conforms to Hashable. It also doesn't work with Int, which also conforms to Hashable.
If I remove the type constraint, just for testing (where I also have to disable the dictionary, as I can't use anything not hashable as key there) - it compiles
class Translator<T> {...} class FooTranslator<String>:Translator<String> {...}
What am I doing wrong?
Generics were introduced to the Java language to provide tighter type checks at compile time and to support generic programming. To implement generics, the Java compiler applies type erasure to: Replace all type parameters in generic types with their bounds or Object if the type parameters are unbounded.
Cannot Create Instances of Type Parameters. Cannot Declare Static Fields Whose Types are Type Parameters. Cannot Use Casts or instanceof With Parameterized Types. Cannot Create Arrays of Parameterized Types.
Generics also provide compile-time type safety that allows programmers to catch invalid types at compile time.
In order to use a generic class, you have to give it a type parameter at compile time, but the type parameter could be of an interface or base class, so the actual, concrete type of the objects used at runtime can vary.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With