Разберите, какой тип имеют add_one и pointer и почему оба значения можно передать в apply.
fn add_one(x: i32) -> i32 {
x + 1
}
fn apply(f: fn(i32) -> i32, x: i32) -> i32 {
f(x)
}
fn main() {
let item = add_one;
let pointer: fn(i32) -> i32 = add_one;
println!("{} {}", apply(item, 4), apply(pointer, 5));
}
add_one как выражение изначально имеет уникальный тип элемента функции (function item type), а pointer явно имеет тип указателя на функцию fn(i32) -> i32. При передаче item в apply Rust выполняет допустимое неявное приведение элемента функции к указателю на функцию, поэтому обе передачи компилируются.
Разделение на тип элемента функции и тип fn позволяет Rust сохранять точную информацию о конкретной функции, не превращая каждое упоминание функции сразу в универсальный указатель. Одновременно приведение к fn обеспечивает удобный способ передавать функции как значения, когда вызывающая сторона ожидает единый тип указателя.
На уровне исходного кода add_one выглядит как обычное значение, но это не переменная с типом fn(i32) -> i32. Для каждой именованной функции существует отдельный, неназываемый тип элемента функции.
Ошибка в рассуждении возникает, когда любой вызываемый объект считают указателем на функцию. Это важно при выводе типов, выборе обобщённой реализации и передаче значения в API: функция, замыкание и указатель fn являются разными категориями значений.
Выражение add_one создаёт значение типа элемента функции, уникального для функции add_one. Такой тип реализует вызов и может быть приведён к fn(i32) -> i32, если функция не является небезопасной и её сигнатура совместима с целевым типом.
В строке let item = add_one; у переменной обычно сохраняется тип элемента функции: у let без явной аннотации нет причины немедленно выполнять это приведение. В вызове apply(item, 4) параметр функции задаёт ожидаемый тип fn(i32) -> i32, поэтому Rust вставляет приведение в этой точке.
В строке let pointer: fn(i32) -> i32 = add_one; целевой тип указан явно, и приведение происходит при инициализации. После этого pointer уже является указателем на функцию, а не элементом функции.
Указатель fn содержит вызываемое значение, пригодное для косвенного вызова. Он не захватывает окружение и поэтому не заменяет замыкание, которому нужно хранить захваченные данные.
У обобщённой функции тип элемента функции может сохраняться, например при параметре F: Fn(i32) -> i32. Если же параметр объявлен именно как fn(i32) -> i32, функция или бесзахватывающее замыкание должны быть приведены к этому типу. Это даёт простой интерфейс, но стирает конкретный тип вызываемого значения и обычно менее гибко, чем обобщение по F.
Допустим, обработчик передаётся в конфигуратор библиотеки. Вариант с параметром fn(Event) -> Result<(), Error> принимает именованные функции и бесзахватывающие замыкания, но не принимает замыкание, которому нужно сохранить состояние счётчика.
Можно использовать fn ради простого ABI-подобного контракта и отсутствия хранения состояния. Можно обобщить API через F: Fn(Event) -> Result<(), Error>: это сохраняет конкретный тип вызываемого значения и поддерживает захваты, но усложняет сигнатуры и иногда увеличивает число мономорфизированных вариантов.
Для обработчика, которому не нужно состояние и который должен быть заменяемым указателем на функцию, выбран fn. Если обработчик конфигурируется замыканием с захватом, правильнее выбрать обобщённый параметр F или объект трейта Box<dyn Fn(...)>, осознанно принимая соответствующие компромиссы. В результате тип API отражает реальные требования к вызываемому значению, а не случайно маскирует их.
let item = add_one; немедленно превращает функцию в fn?Нет. Без подходящего ожидаемого типа Rust может сохранить тип элемента функции. Приведение выполняется в контексте, где требуется тип fn, например при передаче аргумента в apply или при явной аннотации переменной.
fn(i32) -> i32?Нет. Подходит только замыкание без захватов, потому что его можно представить как обычную функцию. Замыкание с захватами хранит состояние и имеет собственный тип, поэтому для него нужен параметр F: Fn(...), FnMut(...), FnOnce(...) либо объект соответствующего трейта.
Нет, типы их элементов функции различаются: каждая функция имеет собственный уникальный тип. Они могут быть приведены к одному типу указателя fn(...) -> ..., а при использовании обобщённого параметра F Rust обычно рассматривает их как разные конкретные типы.