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. Один пример — типы чисел с плавающей точкой: реализация чисел с плавающей точкой утверждает, что два экземпляра значения not-a-number (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 и EqEq требует PartialEq). Когда он выводится для структур и перечислений, cmp ведет себя так же, как выведенная реализация partial_cmp для PartialOrd.

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

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

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

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

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

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

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

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