Приложение C: Выводимые типажи (Derivable Traits)
В различных разделах книги мы обсуждали атрибут derive, который можно применять к определениям структур или перечислений. Атрибут derive генерирует код, который реализует типаж с его стандартной реализацией для типа, помеченного синтаксисом derive.
В этом приложении мы предоставляем справочник по всем типажам стандартной библиотеки, которые можно использовать с derive. Каждый раздел охватывает:
- Какие операторы и методы становятся доступными при выводе этого типажа
- Что делает реализация типажа, предоставляемая
derive - Что означает реализация типажа для типа
- Условия, при которых разрешена или запрещена реализация типажа
- Примеры операций, которые требуют наличия типажа
Если вам нужно поведение, отличное от предоставляемого атрибутом derive, обратитесь к документации стандартной библиотеки для каждого типажа за подробностями о том, как реализовать их вручную.
Перечисленные здесь типажи — это единственные типажи, определенные стандартной библиотекой, которые могут быть реализованы для ваших типов с помощью derive. Другие типажи, определенные в стандартной библиотеке, не имеют осмысленного поведения по умолчанию, поэтому вам решать, как их реализовать, в зависимости от ваших целей.
Пример типажа, который нельзя вывести — это Display, который обрабатывает форматирование для конечных пользователей. Вы всегда должны продумывать подходящий способ отображения типа для конечного пользователя. Какие части типа должен видеть конечный пользователь? Какие части будут для него релевантны? В каком формате данные будут наиболее полезны? Компилятор Rust не обладает таким пониманием, поэтому не может предоставить вам подходящее поведение по умолчанию.
Список выводимых типажей, приведенный в этом приложении, не является исчерпывающим: библиотеки могут реализовывать derive для своих собственных типажей, делая список типажей, которые можно использовать с derive, практически бесконечным. Реализация derive включает использование процедурных макросов, которые рассматриваются в разделе "Пользовательские процедурные макросы" в Главе 20.
Debug для вывода отладочной информации
Типаж Debug включает форматирование для отладки в строки формата, которое указывается добавлением :? внутри заполнителей {}.
Типаж Debug позволяет вам выводить экземпляры типа для целей отладки, чтобы вы и другие программисты, использующие ваш тип, могли исследовать экземпляр в определенной точке выполнения программы.
Типаж Debug требуется, например, при использовании макроса assert_eq!. Этот макрос выводит значения экземпляров, переданных в качестве аргументов, если проверка на равенство не выполняется, чтобы программисты могли увидеть, почему два экземпляра не равны.
PartialEq и Eq для сравнения на равенство
Типаж PartialEq позволяет сравнивать экземпляры типа для проверки на равенство и enables использование операторов == и !=.
Вывод 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.
Пример, когда требуется Clone — это при вызове метода to_vec для среза. Срез не владеет экземплярами типов, которые он содержит, но вектор, возвращаемый из to_vec, должен будет владеть своими экземплярами, поэтому to_vec вызывает clone для каждого элемента. Таким образом, тип, хранящийся в срезе, должен реализовывать Clone.
Типаж Copy позволяет вам дублировать значение, просто копируя биты, хранящиеся в стеке; не требуется выполнения произвольного кода. См. раздел "Данные только в стеке: Copy" в Главе 4 для получения дополнительной информации о Copy.
Типаж 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>.