Примитивный тип fn

Указатели на функции, например fn(usize) -> bool.

Смотрите также трейты Fn, FnMut и FnOnce.

Указатели на функции — это указатели, которые указывают на код, а не на данные. Их можно вызывать так же, как функции. Как и ссылки, указатели на функции предполагаются ненулевыми, поэтому если вы хотите передать указатель на функцию через FFI и иметь возможность обрабатывать нулевые указатели, используйте тип Option<fn()> с требуемой сигнатурой.

Примечание: FFI требует дополнительной осторожности, чтобы гарантировать совпадение ABI для обеих сторон вызова. Точные требования в настоящее время не документированы.

Безопасность

Обычные указатели на функции получаются приведением либо обычных функций, либо замыканий, которые не захватывают окружение:

#![allow(unused)]
fn main() {
fn add_one(x: usize) -> usize {
    x + 1
}

let ptr: fn(usize) -> usize = add_one;
assert_eq!(ptr(5), 6);

let clos: fn(usize) -> usize = |x| x + 5;
assert_eq!(clos(5), 10);
}

Помимо различий в зависимости от сигнатуры, указатели на функции бывают двух видов: безопасные и небезопасные. Обычные указатели fn() могут указывать только на безопасные функции, тогда как небезопасные указатели unsafe fn() могут указывать как на безопасные, так и на небезопасные функции.

#![allow(unused)]
fn main() {
fn add_one(x: usize) -> usize {
    x + 1
}

unsafe fn add_one_unsafely(x: usize) -> usize {
    x + 1
}

let safe_ptr: fn(usize) -> usize = add_one;

// ОШИБКА: несовпадение типов: ожидалась обычная fn, найдена unsafe fn
// let bad_ptr: fn(usize) -> usize = add_one_unsafely;

let unsafe_ptr: unsafe fn(usize) -> usize = add_one_unsafely;
let really_safe_ptr: unsafe fn(usize) -> usize = add_one;
}

ABI

Кроме того, указатели на функции могут различаться в зависимости от используемого ABI. Это достигается добавлением ключевого слова extern перед типом, за которым следует соответствующий ABI. ABI по умолчанию — "Rust", т.е. fn() — это точно такой же тип, как extern "Rust" fn(). Указатель на функцию с ABI C будет иметь тип extern "C" fn().

Блоки extern "ABI" { ... } объявляют функции с ABI "ABI". Значение по умолчанию здесь — "C", т.е. функции, объявленные в блоке extern { ... }, имеют ABI "C".

Для получения дополнительной информации и списка поддерживаемых ABI см. раздел nomicon о соглашениях о вызовах.

Вариативные функции

Объявления внешних функций с ABI "C" или "cdecl" также могут быть вариативными, позволяя вызывать их с переменным числом аргументов. Обычные функции Rust, даже с extern "ABI", не могут быть вариативными. Для получения дополнительной информации см. раздел nomicon о вариативных функциях.

Создание указателей на функции

Когда bar — имя функции, выражение bar не является указателем на функцию. Скорее, оно обозначает значение неименуемого типа, который уникально идентифицирует функцию bar. Значение имеет нулевой размер, потому что тип уже идентифицирует функцию. Это имеет то преимущество, что "вызов" значения (оно реализует трейты Fn*) не требует динамической диспетчеризации.

Этот тип с нулевым размером приводится к обычному указателю на функцию. Например:

#![allow(unused)]
fn main() {
fn bar(x: i32) {}

let not_bar_ptr = bar; // `not_bar_ptr` имеет нулевой размер, уникально идентифицируя `bar`
assert_eq!(size_of_val(&not_bar_ptr), 0);

let bar_ptr: fn(i32) = not_bar_ptr; // принудительное приведение к указателю на функцию
assert_eq!(size_of_val(&bar_ptr), size_of::<usize>());

let footgun = &bar; // это общая ссылка на тип с нулевым размером, идентифицирующий `bar`
}

Последняя строка показывает, что &bar также не является указателем на функцию. Скорее, это ссылка на специфичный для функции ZST (тип с нулевым размером). &bar практически никогда не является тем, что вам нужно, когда bar — функция.

Приведение к целым числам и обратно

Вы можете приводить указатели на функции непосредственно к целым числам:

#![allow(unused)]
fn main() {
let fnptr: fn(i32) -> i32 = |x| x+2;
let fnptr_addr = fnptr as usize;
}

Однако прямое приведение обратно невозможно. Необходимо использовать transmute:

#![allow(unused)]
fn main() {
let fnptr = fnptr_addr as *const ();
let fnptr: fn(i32) -> i32 = unsafe { std::mem::transmute(fnptr) };
assert_eq!(fnptr(40), 42);
}

Ключевой момент: мы сначала приводим к сырому указателю (as-приведение), а затем используем transmute для получения указателя на функцию. Это позволяет избежать transmute из целого числа в указатель, что может быть проблематично. Преобразование между сырыми указателями и указателями на функции (т.е. между двумя типами указателей) допустимо.

Примечание: Все это не переносимо на платформы, где указатели на функции и указатели на данные имеют разный размер.

Совместимость ABI

В общем случае, когда функция объявлена с одной сигнатурой, а вызывается через указатель на функцию с другой сигнатурой, эти две сигнатуры должны быть ABI-совместимыми, иначе вызов функции через этот указатель является неопределенным поведением. Совместимость ABI — гораздо более строгое требование, чем просто одинаковое размещение в памяти; например, даже если i32 и f32 имеют одинаковый размер и выравнивание, они могут передаваться в разных регистрах и, следовательно, не быть ABI-совместимыми.

Проблема совместимости ABI возникает только в коде, который изменяет тип указателей на функции, и в коде, который импортирует функции через блоки extern. Изменение типа указателей на функции чрезвычайно небезопасно (намного более небезопасно, чем даже transmute_copy), и должно происходить только в самых исключительных обстоятельствах. Большая часть кода на Rust просто импортирует функции через use. Так что, скорее всего, вам не нужно беспокоиться о совместимости ABI.

Но предположим такие обстоятельства, каковы правила? В этом разделе мы рассматриваем только ABI прямых вызовов Rust-to-Rust (когда и определение, и место вызова видны компилятору Rust), а не компоновку в целом — как только функции импортируются через блоки extern, появляются дополнительные факторы, которые мы здесь не рассматриваем. Обратите внимание, что это также относится к передаче/вызову функций через языковые границы с помощью указателей на функции.

Ничто в этом разделе не следует воспринимать как гарантию для вызовов не-Rust-to-Rust, даже с типами из core::ffi или libc.

Чтобы две сигнатуры считались ABI-совместимыми, они должны использовать совместимую строку ABI, принимать одинаковое количество аргументов, а типы отдельных аргументов и возвращаемый тип должны быть ABI-совместимыми. Строка ABI объявляется через extern "ABI" fn(...) -> ...; обратите внимание, что fn name(...) -> ... неявно использует строку ABI "Rust", а extern fn name(...) -> ... неявно использует строку ABI "C".

Строки ABI гарантированно совместимы, если они одинаковы, или если строка ABI вызывающей стороны имеет вид $X-unwind, а строка ABI вызываемой функции — $X, где $X — один из следующих: "C", "aapcs", "fastcall", "stdcall", "system", "sysv64", "thiscall", "vectorcall", "win64".

Следующие типы гарантированно ABI-совместимы:

Группа типовУсловия совместимости
*const T, *mut T, &T, &mut T, Box<T> (только Box<T, Global>), NonNull<T>Все совместимы друг с другом для всех T. Также совместимы друг с другом для разных T, если они имеют одинаковый тип метаданных (<T as Pointee>::Metadata)
usizeСовместим с беззнаковым целым типом uN того же размера
isizeСовместим со знакомым целым типом iN того же размера
charСовместим с u32
Указатели на функции (fn)Любые два типа fn совместимы друг с другом, если они имеют одинаковую строку ABI или строка ABI отличается только суффиксом -unwind, независимо от остальной части их сигнатуры
Типы с нулевым размеромЛюбые два типа с размером 0 и выравниванием 1 совместимы
repr(transparent) тип TСовместим со своим уникальным нетривиальным полем (единственным полем, которое не имеет размера 0 и выравнивания 1, если такое поле существует)
i32 и NonZero<i32>Совместимы (аналогично для всех других целочисленных типов)
Тип T и перечисление EЕсли T гарантированно подвергается оптимизации нулевого указателя, а E — перечисление, удовлетворяющее требованиям "option-like"

Option-like перечисления (E) должны удовлетворять следующим требованиям:

  • Используют представление Rust (без модификаторов align или packed)
  • Имеют ровно два варианта
  • Один вариант имеет ровно одно поле типа T
  • Все поля другого варианта имеют нулевой размер с выравниванием в 1 байт

Кроме того, совместимость ABI удовлетворяет следующим общим свойствам:

  • Каждый тип ABI-совместим с самим собой
  • Если T1 и T2 ABI-совместимы, и T2 и T3 ABI-совместимы, то T1 и T3 также ABI-совместимы (транзитивность)
  • Если T1 и T2 ABI-совместимы, то T2 и T1 также ABI-совместимы (симметричность)

Больше сигнатур могут быть ABI-совместимыми на конкретных целевых платформах, но на это нельзя полагаться, поскольку это не переносимо и не является стабильной гарантией.

Заметные случаи типов, которые в общем случае НЕ являются ABI-совместимыми:

  • bool и u8, i32 и u32, char и i32: на некоторых платформах соглашения о вызовах для этих типов различаются в терминах гарантий для оставшихся битов в регистре, которые не используются значением
  • i32 и f32 также несовместимы, как уже упоминалось выше
  • struct Foo(u32) и u32 несовместимы (без repr(transparent)), поскольку структуры являются агрегатными типами и часто передаются иначе, чем примитивы вроде i32

Важное замечание: Эти правила описывают, когда два полностью известных типа являются ABI-совместимыми. При рассмотрении совместимости ABI типа, объявленного в другом крейте (включая стандартную библиотеку), учтите, что любой тип, имеющий приватное поле или атрибут #[non_exhaustive], может изменить своё размещение как нефункциональное обновление, если не указано иное. Поэтому, например, даже если такой тип в настоящее время является 1-ZST или repr(transparent), это может измениться с любой версией библиотеки.

Если объявленная сигнатура и сигнатура указателя на функцию ABI-совместимы, то вызов функции ведёт себя так, как если бы каждый аргумент был преобразован (transmuted) из типа в указателе на функцию в тип в объявлении функции, а возвращаемое значение преобразовывалось из типа в объявлении в тип в указателе. Применяются все обычные предостережения и опасения относительно преобразования типов; например, если функция ожидает NonZero<i32>, а указатель на функцию использует ABI-совместимый тип Option<NonZero<i32>>, и значение, используемое для аргумента, — None, то этот вызов является неопределённым поведением, поскольку преобразование None::<NonZero<i32>> в NonZero<i32> нарушает требование ненулевого значения.

Реализации трейтов

В этой документации сокращение fn(T₁, T₂, …, Tₙ) используется для представления невариативных указателей на функции различной длины. Обратите внимание, что это удобное обозначение для избежания повторяющейся документации, а не допустимый синтаксис Rust.

Следующие трейты реализованы для указателей на функции с любым количеством аргументов и любым ABI:

ТрейтОписание
PartialEqЧастичное сравнение
EqПолное сравнение (эквивалентность)
PartialOrdЧастичное упорядочение
OrdПолное упорядочение
HashХеширование
PointerФорматирование как указатель
DebugОтладочное форматирование
CloneКлонирование
CopyКопирование
SendБезопасная передача между потоками
SyncБезопасный доступ из нескольких потоков
UnpinВозможность перемещения из закреплённого состояния
UnwindSafeБезопасность при раскрутке стека
RefUnwindSafeБезопасность ссылок при раскрутке

Примечание: Хотя этот тип реализует PartialEq, сравнение указателей на функции ненадёжно: указатели на одну и ту же функцию могут сравниваться как неравные (потому что функции дублируются в нескольких единицах компиляции), а указатели на разные функции могут сравниваться как равные (поскольку идентичные функции могут быть дедуплицированы внутри единицы компиляции).

Кроме того, все безопасные указатели на функции реализуют Fn, FnMut и FnOnce, потому что эти трейты специально известны компилятору.

Автоматические реализации трейтов

#![allow(unused)]
fn main() {
impl<Ret, T> Freeze for fn(T₁, T₂, …, Tₙ) -> Ret
impl<Ret, T> RefUnwindSafe for fn(T₁, T₂, …, Tₙ) -> Ret
impl<Ret, T> Send for fn(T₁, T₂, …, Tₙ) -> Ret
impl<Ret, T> Sync for fn(T₁, T₂, …, Tₙ) -> Ret
impl<Ret, T> Unpin for fn(T₁, T₂, …, Tₙ) -> Ret
impl<Ret, T> UnwindSafe for fn(T₁, T₂, …, Tₙ) -> Ret
}

Стандартные реализации (Blanket Implementations)

ТрейтДляОписание
AnyT: 'static + ?SizedДинамическая идентификация типа
Borrow<T>T: ?SizedЗаимствование
BorrowMut<T>T: ?SizedИзменяемое заимствование
CloneToUninitT: CloneКлонирование в неинициализированную память
DebugF: FnPtrОтладочное форматирование для указателей на функции
From<T>Любой TПреобразование из себя в себя
HashF: FnPtrХеширование для указателей на функции
Into<U>U: From<T>Преобразование в другой тип
OrdF: FnPtrУпорядочение для указателей на функции
PartialEqF: FnPtrСравнение для указателей на функции
PartialOrdF: FnPtrЧастичное упорядочение для указателей на функции
PatternF: FnMut(char) -> boolИспользование как шаблона для строк
PointerF: FnPtrФорматирование как указателя
ToOwnedT: CloneПолучение владения
TryFrom<U>U: Into<T>Попытка преобразования
TryInto<U>U: TryFrom<T>Попытка преобразования
EqF: FnPtrЭквивалентность для указателей на функции

Примеры использования

#![allow(unused)]
fn main() {
// Базовое использование указателей на функции
fn multiply(x: i32, y: i32) -> i32 {
    x * y
}

let func_ptr: fn(i32, i32) -> i32 = multiply;
assert_eq!(func_ptr(5, 3), 15);

// Замыкания без захвата окружения также могут быть указателями на функции
let add = |x, y| x + y;
let add_ptr: fn(i32, i32) -> i32 = add;
assert_eq!(add_ptr(2, 3), 5);

// Использование с ABI
extern "C" fn c_style() -> i32 {
    42
}

let c_ptr: extern "C" fn() -> i32 = c_style;

// Указатели на функции как параметры
fn apply_twice(f: fn(i32) -> i32, x: i32) -> i32 {
    f(f(x))
}

fn square(x: i32) -> i32 {
    x * x
}

assert_eq!(apply_twice(square, 2), 16); // ((2²)²) = 16

// Указатели на функции в структурах
struct Operation {
    op: fn(i32, i32) -> i32,
    name: &'static str,
}

let operations = [
    Operation { op: |x, y| x + y, name: "add" },
    Operation { op: |x, y| x - y, name: "subtract" },
    Operation { op: |x, y| x * y, name: "multiply" },
];

for op in &operations {
    println!("{}: {}", op.name, (op.op)(10, 5));
}
}

Важные замечания

  1. Указатели на функции не являются замыканиями — они не могут захватывать окружение
  2. Отсутствие динамической диспетчеризации — вызов указателей на функции обычно происходит без накладных расходов
  3. ABI имеет значение — при межъязыковом взаимодействии необходимо указывать правильный ABI
  4. Ненадёжное сравнение — не следует полагаться на равенство указателей на функции
  5. Приведение типов — требует осторожности и обычно выполняется через transmute