Ключевое слово trait

Общий интерфейс для группы типов.

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

Трейты могут состоять из трёх видов ассоциированных элементов:

  • функции и методы
  • типы
  • константы

Трейты также могут содержать дополнительные параметры типов. Эти параметры типов или сам трейт могут быть ограничены другими трейтами.

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

См. Справочник для получения дополнительной информации о трейтах.

Примеры

Трейты объявляются с помощью ключевого слова trait. Типы могут реализовывать их с помощью impl Trait for Type:

#![allow(unused)]
fn main() {
trait Zero {
    const ZERO: Self;
    fn is_zero(&self) -> bool;
}

impl Zero for i32 {
    const ZERO: Self = 0;

    fn is_zero(&self) -> bool {
        *self == Self::ZERO
    }
}

assert_eq!(i32::ZERO, 0);
assert!(i32::ZERO.is_zero());
assert!(!4.is_zero());
}

С ассоциированным типом:

#![allow(unused)]
fn main() {
trait Builder {
    type Built;

    fn build(&self) -> Self::Built;
}
}

Трейты могут быть обобщёнными, с ограничениями или без:

#![allow(unused)]
fn main() {
trait MaybeFrom<T> {
    fn maybe_from(value: T) -> Option<Self>
    where
        Self: Sized;
}
}

Трейты могут строиться на требованиях других трейтов. В примере ниже Iterator является супертрейтом, а ThreeIterator — субтрейтом:

#![allow(unused)]
fn main() {
trait ThreeIterator: Iterator {
    fn next_three(&mut self) -> Option<[Self::Item; 3]>;
}
}

Трейты могут использоваться в функциях как параметры:

#![allow(unused)]
fn main() {
fn debug_iter<I: Iterator>(it: I) where I::Item: std::fmt::Debug {
    for elem in it {
        println!("{elem:#?}");
    }
}

// u8_len_1, u8_len_2 и u8_len_3 эквивалентны

fn u8_len_1(val: impl Into<Vec<u8>>) -> usize {
    val.into().len()
}

fn u8_len_2<T: Into<Vec<u8>>>(val: T) -> usize {
    val.into().len()
}

fn u8_len_3<T>(val: T) -> usize
where
    T: Into<Vec<u8>>,
{
    val.into().len()
}
}

Или как возвращаемые типы:

#![allow(unused)]
fn main() {
fn from_zero_to(v: u8) -> impl Iterator<Item = u8> {
    (0..v).into_iter()
}
}

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

Трейт-объекты

Трейт-объект — это непрозрачное значение другого типа, которое реализует набор трейтов. Трейт-объект реализует все указанные трейты, а также их супертрейты (если есть).

Синтаксис следующий: dyn BaseTrait + AutoTrait1 + ... AutoTraitN. Можно использовать только один BaseTrait, поэтому этот код не скомпилируется: ⓘ

#![allow(unused)]
fn main() {
trait A {}
trait B {}

let _: Box<dyn A + B>;
}

Это также не скомпилируется, так как является синтаксической ошибкой: ⓘ

#![allow(unused)]
fn main() {
trait A {}
trait B {}

let _: Box<dyn A + dyn B>;
}

С другой стороны, это корректно:

#![allow(unused)]
fn main() {
trait A {}

let _: Box<dyn A + Send + Sync>;
}

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

Небезопасные трейты

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

#![allow(unused)]
fn main() {
unsafe trait UnsafeTrait {}

unsafe impl UnsafeTrait for i32 {}
}

Различия между редакциями 2015 и 2018

warning

В редакции 2015 шаблон параметров не был обязательным для трейтов:

#![allow(unused)]
fn main() {
trait Tr {
    fn f(i32);
}
}

Это поведение больше не допустимо в редакции 2018.