О подходах к реализации шаблонов/дженериков.
В C++ всё просто и понятно. Каждый раз, когда вы используете шаблонный тип или шаблон функции с новым типом, компилятор просто генерирует конкретную специализацию для него. Отсюда возникают такие проблемы, как невозможность разделения объявления и определения шаблонных штук в общем случае. Конечно иногда можно костылить, но сейчас не об этом.
Интересно, как это сделано в других языках. Какие проблемы есть в них, а каких нет.
В Java используется стирание типов (type erasure).
public class A<T> {
private T obj;
public A(T t){
obj = t;
}
public T getObj() {
return obj;
}
}Однако в рантайме дженерик-типа не остаётся. Класс будет существовать в таком виде:
public class A {
private Object obj;
public A(Object t){
obj = t;
}
public Object getObj() {
return obj;
}
}Т.е. в дженерик-типе/функции T заменяется на Object, а в месте использования происходит следующее:
A<String> a = new A<>("qwe");
var aa = a.get();заменится на
A a = new A("qwe");
var aa = (String) a.get();Т.е. снаружи есть информация о том, какой тип отдавался в дженерик, что позволяет сделать нужный каст. И тут есть одно очевидное преимущество: вы можете не перекомпилировать пакет с определением дженерика. Однако сразу можно заметить и недостатки: т.к. происходит стирание типа до
Object (который является родительским типом для всех типов, кроме примитивных), дженерики нельзя использовать с примитивными типами (ха-ха, лохи). Потому и появились типы-обёртки вроде Integer, Boolean и прочих.Хотя это пытаются пофиксить. Как база используется новый вид классов (неожиданно, примитивный). Вы объявляете свой класс с ключевым словом
primitive. Для таких классов появляются правила конвертации в примитивные типы, запрет на наличие значения Null и другие разные правила. Но сейчас не об этом. Пропозал (хз насколько такой термин в сообществе джавы применим). Когда у вас есть примитивные классы, вы можете интерпретировать примитивные типы вроде
int/double и другие как примитивные классы, что позволит реализовать дженерики и для них. Получается, что костылят :( Хотели ввести в Java 19, но не успели, так что мб к Java 20 получится. Линк.
В Rust дженерики работают примерно как в плюсах: на один крейт (единица компиляции в Rust) тип в дженерик подставляется только один раз (грубо говоря, копирование кода для каждого типа в дженерике). Если дженерик из крейта 1 используется в крейте 2, то будет своя инстанцированная версия в крейте 2.
UPD: тема go не раскрыта. Чуть позже вкину подробный пост про это в качестве исправления.