Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why generics don't compile?

Tags:

generics

swift

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?

like image 868
User Avatar asked Jan 13 '15 10:01

User


People also ask

Are generics compile time?

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.

What is the disadvantages of using generics?

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.

Do generics provide compile time safety?

Generics also provide compile-time type safety that allows programmers to catch invalid types at compile time.

Are generics applied at compile time or run 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.


1 Answers

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.

like image 186
nhgrif Avatar answered Oct 04 '22 09:10

nhgrif