Generics: Erasure

The ability to parameterize types via generics provides strong readability benefits for developers and users of your code. It also provides the typechecker the necessary information to ensure that you are the code in the way it was intended. For example, having a Box<int> as a parameter type to a function clearly shows the type that the Box should contain and the typechecker can use this information as well.

However, with the exception of the return type for async functions (Awaitable<T>), support for generics is only at the typechecker level via type annotations; they do not exist at runtime. Generic type parameters and arguments and stripped out (i.e., erased) before execution. Using our Box example above, this means that Box<int> really becomes Box at runtime, and HHVM will allow you to pass non-int Boxes to the function.

<?hh

namespace Hack\UserDocumentation\Generics\Erasure\Examples\TypeErasure;

class Box<T> {
  private T $x;
  public function __construct(T $x) {
    $this->x = $x;
  }
  public function get(): T {
    return $this->x;
  }
}

function foo(Box<int> $b): void {
  var_dump($b);
}

function run(): void {
  $b = new Box(4);
  $c = new Box("hi");
  foo($b);
  foo($c);
}

run();

/*****
* Typechecker error:
*
* File "type-erasure.php", line 23, characters 7-8:
* Invalid argument (Typing[4110])
* File "type-erasure.php", line 15, characters 18-20:
* This is an int
* File "type-erasure.php", line 21, characters 16-19:
* It is incompatible with a string
* File "type-erasure.php", line 15, characters 18-20:
* Considering that this type argument is invariant with respect to Hack\UserDocumentation\Generics\Erasure\Examples\TypeErasure\Box
****/
Output
object(Hack\UserDocumentation\Generics\Erasure\Examples\TypeErasure\Box)#1 (1) {
  ["x":"Hack\UserDocumentation\Generics\Erasure\Examples\TypeErasure\Box":private]=>
  int(4)
}
object(Hack\UserDocumentation\Generics\Erasure\Examples\TypeErasure\Box)#2 (1) {
  ["x":"Hack\UserDocumentation\Generics\Erasure\Examples\TypeErasure\Box":private]=>
  string(2) "hi"
}

While having the generic parameterization is certainly a great benefit, there are some limitations you need to be aware of because of type erasure at runtime. A type parameter T cannot be used in the following situations:

  • Creating instances using new (e.g., new T()).
  • Casting (e.g., (T) $value).
  • In a class scope, e.g., T::aStaticMethod().
  • As the right-hand side of an instanceof check.
  • As the type of a static property.
  • As the type of the exception in a catch block (e.g., catch (T $exception)).

For a possible alternative to instantiation, class scope and instanceof usage, Hack provides a construct called Classname<T> that extends the PHP representation of Foo::class.