Почему обобщённую функцию нельзя сохранить как значение без выбора конкретных типов параметров?
Обобщённая функция — это не одно готовое значение-функция, а описание, из которого компилятор создаёт специализированный вариант для конкретных типов. Поэтому перед сохранением её в переменную или передачей как указателя на функцию типы параметров должны быть выведены или указаны явно.
После специализации функция может быть преобразована в обычный указатель на функцию с конкретной сигнатурой.
Обобщения в Rust предназначены для статически типобезопасного переиспользования кода без обязательных накладных расходов динамической диспетчеризации. Для этого вызовы generic-функций обычно разрешаются во время компиляции, а не через универсальный runtime-объект.
Указатель на функцию, напротив, должен иметь одну полностью определённую сигнатуру: компилятор должен знать типы аргументов и результата. Абстрактная функция с ещё не выбранным параметром типа такой сигнатурой не обладает.
Рассмотрим функцию, которая возвращает свой аргумент. Если попытаться сохранить её без контекста ожидаемого типа, неизвестно, какой именно вариант нужен: для целого числа, строки или другого типа.
Это ограничение важно отличать от невозможности использовать generic-функцию как аргумент вообще. Если контекст вызова задаёт типы, компилятор может вывести их и создать нужную специализацию. Ошибка возникает именно при отсутствии такого контекста.
В объявлении identity параметр T ещё неизвестен. Аннотация типа переменной требует конкретную сигнатуру fn(u32) -> u32, поэтому Rust выбирает специализацию identity::<u32> и затем выполняет преобразование в указатель на функцию.
Запись без ожидаемого типа, например присваивание generic-функции переменной без аннотации, не даёт компилятору определить T. Напротив, передача функции непосредственно в apply может сработать, поскольку сигнатура параметра apply предоставляет необходимый контекст.
Следует различать function item и function pointer. Специализированная функция в исходной позиции имеет собственный тип function item; он нулевого размера и содержит информацию о конкретной функции. При необходимости такой объект неявно приводится к типу указателя fn(...) -> ....
Указатель на функцию не сохраняет generic-полиморфизм: после выбора u32 тот же указатель нельзя вызвать как вариант для String. Для хранения вызываемых объектов с разными захваченными состояниями обычно используют обобщённые параметры или dyn Fn, но это уже другой механизм диспетчеризации.
В обработчике задач нужно сохранить преобразователь в поле структуры и вызывать его позже. Вариант с generic-полем не подходит напрямую, если одна структура должна принимать разные типы функций во время работы программы: тип поля должен быть единым.
Можно выбрать обычный указатель на функцию, если обработчик не захватывает состояние. Это даёт простое представление и прямой вызов, но ограничивает функции одной сигнатурой и не поддерживает замыкания с окружением.
Другой вариант — Box<dyn Fn(u32) -> u32>. Он принимает разные функции и замыкания, включая захватывающие, но использует динамическую диспетчеризацию и обычно требует выделения памяти при размещении в Box.
Если набор типов известен на этапе компиляции и важна производительность, предпочтителен generic-параметр структуры или функции. Если обработчики выбираются динамически и должны храниться вместе, оправдан trait object. Выбор определяется тем, нужна ли полиморфность во время компиляции или во время выполнения.
Нет. Если тип параметра можно вывести из ожидаемой сигнатуры или других аргументов, компилятор специализирует функцию автоматически. Явное указание типа требуется, когда контекста недостаточно или когда нужно устранить неоднозначность.
fn потерю статической диспетчеризации?Нет. Для generic-функции сначала выбирается конкретная специализация во время компиляции, а затем уже может использоваться указатель на неё. Вызов через fn является косвенным вызовом по адресу, но это не то же самое, что вызов метода через таблицу виртуальных методов trait object.
Обычный указатель на функцию так сделать не может: его сигнатура содержит конкретные типы. Нужно либо иметь отдельную специализацию для каждого типа, либо проектировать API с generic-вызовом на уровне самого контейнера, либо использовать другой тип данных, например перечисление известных вариантов. Trait object также не делает одну функцию полиморфной для произвольных несвязанных типов: его методы должны иметь единую объектную сигнатуру.