Примитивный тип 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(¬_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иT2ABI-совместимы, иT2иT3ABI-совместимы, тоT1иT3также ABI-совместимы (транзитивность) - Если
T1иT2ABI-совместимы, то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)
| Трейт | Для | Описание |
|---|---|---|
Any | T: 'static + ?Sized | Динамическая идентификация типа |
Borrow<T> | T: ?Sized | Заимствование |
BorrowMut<T> | T: ?Sized | Изменяемое заимствование |
CloneToUninit | T: Clone | Клонирование в неинициализированную память |
Debug | F: FnPtr | Отладочное форматирование для указателей на функции |
From<T> | Любой T | Преобразование из себя в себя |
Hash | F: FnPtr | Хеширование для указателей на функции |
Into<U> | U: From<T> | Преобразование в другой тип |
Ord | F: FnPtr | Упорядочение для указателей на функции |
PartialEq | F: FnPtr | Сравнение для указателей на функции |
PartialOrd | F: FnPtr | Частичное упорядочение для указателей на функции |
Pattern | F: FnMut(char) -> bool | Использование как шаблона для строк |
Pointer | F: FnPtr | Форматирование как указателя |
ToOwned | T: Clone | Получение владения |
TryFrom<U> | U: Into<T> | Попытка преобразования |
TryInto<U> | U: TryFrom<T> | Попытка преобразования |
Eq | F: 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)); } }
Важные замечания
- Указатели на функции не являются замыканиями — они не могут захватывать окружение
- Отсутствие динамической диспетчеризации — вызов указателей на функции обычно происходит без накладных расходов
- ABI имеет значение — при межъязыковом взаимодействии необходимо указывать правильный ABI
- Ненадёжное сравнение — не следует полагаться на равенство указателей на функции
- Приведение типов — требует осторожности и обычно выполняется через
transmute