Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Приложение C: выводимые трейты

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

В этом приложении собран справочник по всем трейтам стандартной библиотеки, которые можно использовать с derive. В каждом разделе описано:

  • какие операторы и методы становятся доступны, если вывести этот трейт;
  • что делает реализация трейта, которую предоставляет derive;
  • что реализация трейта говорит о типе;
  • при каких условиях трейт можно или нельзя реализовать;
  • примеры операций, которым нужен этот трейт.

Если вам нужно поведение, отличное от того, что даёт атрибут derive, обратитесь к документации стандартной библиотеки по каждому трейту — там описано, как реализовать их вручную.

Перечисленные здесь трейты — единственные из определённых в стандартной библиотеке, которые можно реализовать для своих типов через derive. У остальных трейтов стандартной библиотеки нет разумного поведения по умолчанию, поэтому реализовывать их вам придётся самим — так, как имеет смысл для вашей задачи.

Пример трейта, который нельзя вывести, — Display: он отвечает за форматирование для конечных пользователей. Всегда стоит продумать, как правильно показывать тип конечному пользователю. Какие части типа ему можно видеть? Какие будут для него важны? Какой формат данных будет ему наиболее полезен? Компилятор Rust этого не знает, поэтому не может предоставить за вас подходящее поведение по умолчанию.

Список выводимых трейтов в этом приложении не исчерпывающий: библиотеки могут реализовать derive для своих трейтов, так что список трейтов, с которыми можно использовать derive, по-настоящему открыт. Реализация derive требует процедурного макроса — о них рассказано в разделе «Пользовательские макросы derive» главы 20.

Debug — вывод для программиста

Трейт Debug включает отладочное форматирование в строках формата — оно обозначается добавлением :? внутрь заполнителя {}.

Трейт Debug позволяет выводить экземпляры типа для отладки, чтобы вы и другие программисты, использующие ваш тип, могли изучить экземпляр в определённый момент выполнения программы.

Debug нужен, например, макросу assert_eq!. Если проверка на равенство не проходит, этот макрос выводит значения переданных ему экземпляров, чтобы программист видел, почему они не равны.

PartialEq и Eq — сравнение на равенство

Трейт PartialEq позволяет сравнивать экземпляры типа на равенство и включает операторы == и !=.

Вывод PartialEq реализует метод eq. Если PartialEq выведен для структуры, два экземпляра равны, только если равны все поля, и не равны, если не равно хотя бы одно поле. Для перечисления каждый вариант равен самому себе и не равен остальным вариантам.

PartialEq нужен, например, макросу assert_eq!, которому надо уметь сравнивать два экземпляра типа на равенство.

У трейта Eq нет методов. Его назначение — сообщить, что любое значение помеченного типа равно самому себе. Eq можно применить только к типам, которые реализуют и PartialEq, хотя не каждый тип, реализующий PartialEq, может реализовать Eq. Пример — числа с плавающей точкой: по их реализации два экземпляра значения «не число» (NaN) не равны друг другу.

Eq нужен, например, ключам в HashMap<K, V> — чтобы HashMap<K, V> могла определить, одинаковы ли два ключа.

PartialOrd и Ord — сравнение порядка

Трейт PartialOrd позволяет сравнивать экземпляры типа, чтобы их можно было сортировать. Тип, реализующий PartialOrd, можно использовать с операторами <, >, <= и >=. PartialOrd можно применить только к типам, которые реализуют и PartialEq.

Вывод PartialOrd реализует метод partial_cmp, который возвращает Option<Ordering>; результат будет None, если для переданных значений порядок не определён. Пример значения, для которого порядок не определён, хотя большинство значений этого типа сравнимы, — значение с плавающей точкой NaN. Вызов partial_cmp для любого числа с плавающей точкой и NaN вернёт None.

Для структур выведенный PartialOrd сравнивает два экземпляра поле за полем в том порядке, в котором поля перечислены в определении структуры. Для перечислений варианты, объявленные раньше, считаются меньше вариантов, объявленных позже.

PartialOrd нужен, например, методу gen_range из крейта rand, который генерирует случайное значение в диапазоне, заданном выражением диапазона.

Трейт Ord гарантирует, что для любых двух значений помеченного типа существует корректный порядок. Ord реализует метод cmp, который возвращает Ordering, а не Option<Ordering>, потому что корректный порядок всегда определён. Ord можно применить только к типам, которые реализуют и PartialOrd, и Eq (а Eq требует PartialEq). Для структур и перечислений выведенный cmp ведёт себя так же, как выведенная реализация partial_cmp в PartialOrd.

Ord нужен, например, при хранении значений в BTreeSet<T> — структуре данных, которая хранит данные в порядке их сортировки.

Clone и Copy — дублирование значений

Трейт Clone позволяет явно создать глубокую копию значения; при этом может выполняться произвольный код и копироваться данные в куче. Подробнее о Clone — в разделе «Переменные и данные: клонирование» главы 4.

Вывод Clone реализует метод clone, который для типа целиком вызывает clone у каждой его части. Поэтому, чтобы вывести Clone, все поля или значения внутри типа тоже должны реализовывать Clone.

Clone нужен, например, при вызове метода to_vec у среза. Срез не владеет экземплярами, которые содержит, а вектор, возвращаемый to_vec, должен ими владеть, поэтому to_vec вызывает clone для каждого элемента. Значит, тип, хранящийся в срезе, должен реализовывать Clone.

Трейт Copy позволяет продублировать значение, просто скопировав биты, хранящиеся на стеке, — никакого произвольного кода не нужно. Подробнее о Copy — в разделе «Данные только на стеке: копирование» главы 4.

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

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

Copy требуется редко: для типов с Copy доступны оптимизации — не нужно вызывать clone, и код получается лаконичнее.

Всё, что можно сделать с Copy, можно сделать и с Clone, но код может работать медленнее или кое-где придётся вызывать clone.

Hash — отображение значения в значение фиксированного размера

Трейт Hash позволяет взять экземпляр типа произвольного размера и с помощью хеш-функции отобразить его в значение фиксированного размера. Вывод Hash реализует метод hash. Выведенная реализация hash объединяет результаты вызова hash для каждой части типа, поэтому, чтобы вывести Hash, все поля или значения тоже должны реализовывать Hash.

Hash нужен, например, ключам в HashMap<K, V>, чтобы эффективно хранить данные.

Default — значения по умолчанию

Трейт Default позволяет создать для типа значение по умолчанию. Вывод Default реализует функцию default. Выведенная реализация default вызывает default для каждой части типа, поэтому, чтобы вывести Default, все поля или значения внутри типа тоже должны реализовывать Default.

Функцию Default::default часто используют вместе с синтаксисом обновления структуры, о котором говорилось в разделе «Создание экземпляров с синтаксисом обновления» главы 5. Можно задать нужные значения нескольким полям структуры, а для остальных полей взять значения по умолчанию с помощью ..Default::default().

Default нужен, например, при вызове метода unwrap_or_default у экземпляров Option<T>. Если Option<T> равен None, метод unwrap_or_default вернёт результат Default::default для типа T, хранящегося в Option<T>.