Rust перегрузка функций

Обновлено: 02.07.2024

В Rust нет перегрузки функций: вы не можете определить несколько функций с одним и тем же именем, но с разными аргументами.

Однако я обнаружил следующий трюк, из-за которого кажется, что существует перегрузка функций. Обратите внимание, как в main() я могу вызывать example.apply(. ) с разными типами:

Теперь я хотел бы создать две разные версии трейта Apply : одну для значений, которые равны Copy , и одну для значений, которые не равны Copy :

Однако, когда я пытаюсь использовать это, я получаю ошибки:

Компилятор жалуется, что не знает, какую функцию apply вызвать. Я ожидал, что он сможет сделать выбор: i32 не является ссылкой, а является Copy , поэтому apply признака ApplyCopy - единственное, что может быть вызывается, а &NotCopy(34) является ссылкой, а поскольку NotCopy не является Copy , может быть вызван только apply из ApplyRef .

После нескольких попыток задача была успешно решена. Как — под катом.

Игры с типажами не работают.

Попробуем вызвать функцию с аргументом типа &str .

Это не компилируется, ибо вызов неоднозначен и Rust не пытается выяснить, какая их функций — в зависимости от типов/числа аргументов — вызывается. Если мы запустим данный код, компилятор сообщит, что имеется несколько функций, которые можно вызвать в данном случае.

Наоборот, данный пример требует однозначного указания вызываемой функции:

Однако это сводит на нет все преимущества, предоставляемые перегрузкой. В конце этой заметки я покажу, что на Rust реализуема традиционная перегрузка функций — посредством использования типажей и обобщенного программирования — generic'ов.

Интермедия: избыточный generic-код

Опасайтесь засорения избыточным generic-кодом. Если у вас имеется обобщенная функция с большим количеством нетривиального кода, то для каждого вызова этой функции с аргументами разных типов создаются специализированные копии функций. Это происходит, даже если вы каждый раз переводите в начале функции входные аргументы в переменные нужных типов.

К счастью, имеется простое решение проблемы: реализация приватной функции без generic'ов, принимающей типы, с которыми вы хотите работать. В то время как публичные функции производят преобразования типов и передают выполнение вашей приватной функции:

Несмотря на то, что функция вызвана с двумя разными типами ( &[f64] и &Vec<f64> ) основная логика функции реализована (и скомпилирована) только один раз, что предотвращает чрезмерное раздувание бинарников.

Проверяем границы

Не всякая перегрузка подпадает под эту категорию простых преобразований аргументов. Иногда вам действительно требуется разная логика для обработки разных наборов принимаемых аргументов. Для данных случаев вы можете определить свой типаж для реализации программной логики вашей функции:

Это делает типаж очень неуклюжим, ибо self и аргументы поменяны местами:

Типаж не может быть скрыт как деталь реализации. Если вы решите сделать типаж приватным, компилятор выдаст следующее: private trait in public interface .

Давайте сделаем обертку над типажом:

Применение данного приема можно найти в стандартной библиотеке в типаже Pattern , который используется разными функциями, которые ищут или тем или иным образом сопоставляют строки, например, str::find .

Статический полиморфизм

Для разрешения методу принятия различных типов аргументов Rust использует статический полиморфизм с generic'ами.

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

Они могут быть простыми, например, AsRef , чтобы позволить вашему API принимать больше вариантов аргументов:

В вызывающем коде это похоже на перегрузку:

Вероятно, лучшим примером этого является принимающий несколько типов аргументов
типаж ToString :

Эта разновидность перегрузки делает ваш API более удобным для использования вашими пользователями. Им не нужно будет обременять себя переводом аргументов в нужный тип, API этого не требует. В итоге получается API, с которым приятно работать.

Данный подход имеет преимущества по сравнения с привычной перегрузкой, ибо реализация типами (пользователя) типажей позволяет вашему API принимать разные пользовательские типы.

Привычная перегрузка предлагает гораздо больше гибкости в реализации и в количестве принимаемых аргументов в перегруженных функциях. Последняя проблема может быть решена использованием кортежей в качестве вместилища набора аргументов, но это не очень привлекательно. Пример тому — типаж ToSocketAddrs в стандартной библиотеке.

Попасть одним выстрелом в двух зайцев

Есть лучший путь, который даст нам почти все возможности общепринятой перегрузки функций.

Создайте типаж для функции, сигнатуру которой вы желаете перегрузить с обобщенными параметрами на месте "перегружаемых" параметров.

Ограничения на типажи в Rust являются очень мощным инструментом.

При реализации метода, просто ограничьте Self , чтобы он реализовывал типаж и обобщенные параметры, в которых нуждается ваш типаж. Для Rust это достаточно:

После этого реализуйте типаж для всех типов, для которых вы хотите предоставить перегрузку:

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

Вьі перепутали данньіе и методьі. В изначальном примере оно есттественно не работает, потому что метод вьізван от одних данньіх.

В терминах С. Вьі унаследовали два разньіх класса, у которьіх разньіе не связанньіе методьі имеют одно имя. В С компилятор бьі заблокировал такое. В Питоне есть механизм подсчета и вьібора реализации. В Расте, ради ясности, запретили неявное определение.

Должен бьіть один трейт, разньіе структурьі, разньіе имлементацие для разньіх данньіх. И тогда будет работать как в С. Вьізов одной функции и вьібор реализации от типа данньіх.

Это дополнение содержит глоссарий синтаксиса Rust, включая операторы и другие обозначения, которые появляются сами по себе или в контексте путей, обобщений, типажей, макросов, атрибутов, комментариев, кортежей и скобок.

Операторы

Таблица Б-1 содержит операторы языка Rust, пример появления оператора, короткое объяснение, возможность перегрузки оператора. Если оператор можно перегрузить, то показан типаж, с помощью которого его можно перегрузить.

Таблица Б-1: Операторы

ОператорПримерОбъяснениеПерегружаемость
! ident!(. ) , ident! <. >, ident![. ] Вызов макроса
! !expr Побитовое или логическое отрицание Не
!= var != expr Сравнение "не равно" PartialEq
% expr % expr Остаток от деления Rem
%= var %= expr Остаток от деления и присваивание RemAssign
& &expr , &mut expr Заимствование
& &type , &mut type , &'a type , &'a mut type Указывает что данный тип заимствуется
& expr & expr Побитовое И BitAnd
&= var &= expr Побитовое И и присваивание BitAndAssign
&& expr && expr Логическое И
* expr * expr Арифметическое умножение Mul
*= var *= expr Арифметическое умножение и присваивание MulAssign
* *expr Разыменование ссылки
* *const type , *mut type Указывает, что данный тип является сырым указателем
+ trait + trait , 'a + trait Соединение ограничений типа
+ expr + expr Арифметическое сложение Add
+= var += expr Арифметическое сложение и присваивание AddAssign
, expr, expr Аргумент и разделитель элементов
- - expr Арифметическое отрицание Neg
- expr - expr Арифметическое вычитание Sub
- var -= expr Арифметическое вычитание и присваивание SubAssign
-> fn(. ) -> type , .
. expr.ident Доступ к элементу
.. .. , expr.. , ..expr , expr..expr Указывает на диапазон чисел, исключая правый
..= ..=expr , expr..=expr Указывает на диапазон чисел, включая правый
.. ..expr Синтаксис обновления структуры
.. variant(x, ..) , struct_type Привязка «И все остальное»
. expr. expr В шаблоне: шаблон диапазона включая правый элемент
/ expr / expr Арифметическое деление Div
/= var /= expr Арифметическое деление и присваивание DivAssign
: pat: type , ident: type Ограничения типов
: ident: expr Инициализация поля структуры
: 'a: loop Метка цикла
; expr; Оператор, указывающий на конец высказывания
; [. ; len] Часть синтаксиса массива фиксированного размера
<< expr << expr Битовый сдвиг влево Shl
<<= var <<= expr Битовый сдвиг влево и присваивание ShlAssign
< expr < expr Сравнение "меньше чем" PartialOrd
<= expr <= expr Сравнение "меньше или равно" PartialOrd
= var = expr , ident = type Присваивание/эквивалентность
== expr == expr Сравнение "равно" PartialEq
=> pat => expr Часть синтаксиса конструкции match
> expr > expr Сравнение "больше чем" PartialOrd
>= expr >= expr Сравнение "больше или равно" PartialOrd
>> expr >> expr Битовый сдвиг вправо Shr
>>= var >>= expr Битовый сдвиг вправо и присваивание ShrAssign
@ ident @ pat Pattern binding
^ expr ^ expr Побитовое исключающее ИЛИ BitXor
^= var ^= expr Побитовое исключающее ИЛИ и присваивание BitXorAssign
patpat
exprexpr
= var= expr
expr
? expr? Возврат ошибки

Обозначения не-операторы

Следующий список содержит все не-литералы, которые не являются операторами. То есть они не ведут себя как вызов функции или метода.

Таблица Б-2 показывает символы, которые появляются сами по себе и допустимы в различных местах.

Таблица Б-2: Автономный синтаксис

Таблица Б-3 показывает обозначения которые появляются в контексте путей иерархии модулей

Таблица Б-3. Синтаксис, связанный с путями

ОбозначениеОбъяснение
ident::ident Путь к пространству имён
::path Путь относительно корня крейта (т. е. явный абсолютный путь)
self::path Путь относительно текущего модуля (т. е. явный относительный путь).
super::path Путь относительно родительского модуля текущего модуля
type::ident , <type as trait>::ident Ассоциированные константы, функции и типы
<type>. Ассоциированный элемент для типа, который не может быть назван прямо (например <&T>. , <[T]>. , etc.)
trait::method(. ) Устранение неоднозначности вызова метода путём именования типажа, который определяет его
type::method(. ) Устранение неоднозначности путём вызова метода через имя типа, для которого он определён
<type as trait>::method(. ) Устранение неоднозначности вызова метода путём именования типажа и типа

Таблица Б-4 показывает обозначения которые появляются в контексте использования обобщённых типов параметров

Таблица Б-4: Обобщения

ОбозначениеОбъяснение
path<. > Определяет параметры для обобщённых параметров в типе (e.g., Vec<u8> )
path::<. > , method::<. > Определяет параметры для обобщённых параметров, функций, или методов в выражении. Часто называют turbofish (например "42".parse::<i32>() )
fn ident<. > . Определение обобщённой функции
struct ident<. > . Определение обобщённой структуры
enum ident<. > . Объявление обобщённого перечисления
impl<. > . Определение обобщённой реализации
for<. > type Высокоуровневое связывание времени жизни
type<ident=type> Обобщённый тип где один или более ассоциированных типов имеют определённое присваивание (например Iterator<Item=T> )

Таблица Б-5 показывает обозначения которые появляются в контексте использования обобщённых типов параметров с ограничениями типов

Таблица Б-5: Ограничения типов

ОбозначениеОбъяснение
T: U Обобщённый параметр T ограничивается до типов которые реализуют типаж U
T: 'a Обобщённый тип T должен существовать не меньше чем 'a (то есть тип не может иметь ссылки с временем жизни меньше чем 'a )
T : 'static Обобщённый тип T не имеет заимствованных ссылок кроме имеющих время жизни 'static
'b: 'a Обобщённое время жизни 'b должно быть не меньше чем 'a
T: ?Sized Позволяет обобщённым типам параметра иметь динамический размер
'a + trait , trait + trait Соединение ограничений типов

Таблица Б-6 показывает обозначения, которые появляются в контексте вызова или определения макросов и указания атрибутов элемента.

Таблица Б-6: Макросы и атрибуты

Таблица Б-7 показывает обозначения, которые создают комментарии.

Таблица Б-7: Комментарии

ОбозначениеОбъяснение
// Однострочный комментарий
//! Внутренний однострочный комментарий документации
/// Внешний однострочный комментарий документации
/*. */ Многострочный комментарий
/*. */ Внутренний многострочный комментарий документации
/**. */ Внешний многострочный комментарий документации

Таблица Б-8 показывает обозначения, которые появляются в контексте использования кортежей.

Таблица Б-8: Кортежи

ОбозначениеОбъяснение
() Пустой кортеж, он же пустой тип. И литерал и тип.
(expr) Выражение в скобках
(expr,) Кортеж с одним элементом выражения
(type,) Кортеж с одним элементом типа
(expr, . ) Выражение кортежа
(type, . ) Тип кортежа
(type, . ) Выражение вызова функции; также используется для инициализации структур-кортежей и вариантов-кортежей перечисления
expr.0 , expr.1 , etc.Взятие элемента по индексу в кортеже

Таблица Б-9 показывает контексты, в которых используются фигурные скобки.

Таблица Б-9: Фигурные скобки

КонтекстОбъяснение
Выражение блока
Type struct литерал

Таблица Б-10 показывает контексты, в которых используются квадратные скобки.

Читайте также: