Rust перегрузка функций
В 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 |
pat | pat | ||
expr | expr | ||
= | 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 показывает контексты, в которых используются квадратные скобки.
Читайте также: