1. 1. Introduction
  2. 2. Введение
  3. 3. C чего начать
  4. 4. Изучение Rust
    1. 4.1. Угадайка
    2. 4.2. Обедающие философы
    3. 4.3. Вызов кода на Rust из других языков
  5. 5. Синтаксис и семантика
    1. 5.1. Связывание имён
    2. 5.2. Функции
    3. 5.3. Простые типы
    4. 5.4. Комментарии
    5. 5.5. Конструкция `if`
    6. 5.6. Циклы
    7. 5.7. Владение
    8. 5.8. Ссылки и заимствование
    9. 5.9. Время жизни
    10. 5.10. Изменяемость
    11. 5.11. Структуры
    12. 5.12. Перечисления
    13. 5.13. Конструкция `match`
    14. 5.14. Шаблоны сопоставления `match`
    15. 5.15. Синтаксис методов
    16. 5.16. Вектора
    17. 5.17. Строки
    18. 5.18. Обобщённое программирование
    19. 5.19. Типажи
    20. 5.20. Типаж `Drop`
    21. 5.21. Конструкция `if let`
    22. 5.22. Типажи-объекты
    23. 5.23. Замыкания
    24. 5.24. Универсальный синтаксис вызова функций
    25. 5.25. Контейнеры и модули
    26. 5.26. `const` и `static`
    27. 5.27. Атрибуты
    28. 5.28. Псевдонимы типов
    29. 5.29. Приведение типов
    30. 5.30. Ассоциированные типы
    31. 5.31. Безразмерные типы
    32. 5.32. Перегрузка операций
    33. 5.33. Преобразования при разыменовании
    34. 5.34. Макросы
    35. 5.35. Сырые указатели
    36. 5.36. Небезопасный код
  6. 6. Эффективное использование Rust
    1. 6.1. Стек и куча
    2. 6.2. Тестирование
    3. 6.3. Условная компиляция
    4. 6.4. Документация
    5. 6.5. Итераторы
    6. 6.6. Многозадачность
    7. 6.7. Обработка ошибок
    8. 6.8. Выбор гарантий
    9. 6.9. Интерфейс внешних функций
    10. 6.10. Типажи `Borrow` и `AsRef`
    11. 6.11. Каналы сборок
    12. 6.12. Using Rust without the standard library
  7. 7. Нестабильные возможности Rust
    1. 7.1. Плагины к компилятору
    2. 7.2. Встроенный ассемблерный код
    3. 7.3. Без stdlib
    4. 7.4. Внутренние средства
    5. 7.5. Элементы языка
    6. 7.6. Продвинутое руководство по компоновке
    7. 7.7. Тесты производительности
    8. 7.8. Синтаксис упаковки и шаблоны `match`
    9. 7.9. Шаблоны `match` для срезов
    10. 7.10. Ассоциированные константы
    11. 7.11. Пользовательские менеджеры памяти
  8. 8. Глоссарий
  9. 9. Syntax Index
  10. 10. Библиография

Ассоциированные типы

Ассоциированные (связанные) типы — это мощная часть системы типов в Rust. Они связаны с идеей 'семейства типа', другими словами, группировки различных типов вместе. Это описание немного абстрактно, так что давайте разберем на примере. Если вы хотите написать типаж Graph, то нужны два обобщенных параметра типа: тип узел и тип ребро. Исходя из этого, вы можете написать типаж Graph<N, E>, который выглядит следующим образом:

trait Graph<N, E> {
    fn has_edge(&self, &N, &N) -> bool;
    fn edges(&self, &N) -> Vec<E>;
    // etc
}Run

Такое решение вроде бы достигает своей цели, но, в конечном счете, является неудобным. Например, любая функция, которая принимает Graph в качестве параметра, также должна быть обобщённой с параметрами N и E:

fn distance<N, E, G: Graph<N, E>>(graph: &G, start: &N, end: &N) -> u32 { 
    ... 
}Run

Наша функция расчета расстояния работает независимо от типа Edge, поэтому параметр E в этой сигнатуре является лишним и только отвлекает.

Что действительно нужно заявить, это чтобы сформировать какого-либо вида Graph, нужны соответствующие типы E и N, собранные вместе. Мы можем сделать это с помощью ассоциированных типов:

trait Graph {
    type N;
    type E;

    fn has_edge(&self, &Self::N, &Self::N) -> bool;
    fn edges(&self, &Self::N) -> Vec<Self::E>;
    // etc
}Run

Теперь наши клиенты могут абстрагироваться от определенного Graph:

fn distance<G: Graph>(graph: &G, start: &G::N, end: &G::N) -> u32 { ... }Run

Больше нет необходимости иметь дело с типом E!

Давайте поговорим обо всем этом более подробно.

Определение ассоциированных типов

Давайте построим наш типаж Graph. Вот его определение:

trait Graph {
    type N;
    type E;

    fn has_edge(&self, &Self::N, &Self::N) -> bool;
    fn edges(&self, &Self::N) -> Vec<Self::E>;
}Run

Достаточно просто. Ассоциированные типы используют ключевое слово type, и расположены внутри тела типажа, наряду с функциями.

Эти объявления type могут иметь все то же самое, как и при работе с функциями. Например, если бы мы хотели, чтобы тип N реализовывал Display, чтобы была возможность печатать узлы, мы могли бы сделать следующее:

use std::fmt;

trait Graph {
    type N: fmt::Display;
    type E;

    fn has_edge(&self, &Self::N, &Self::N) -> bool;
    fn edges(&self, &Self::N) -> Vec<Self::E>;
}Run

Реализация ассоциированных типов

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

struct Node;

struct Edge;

struct MyGraph;

impl Graph for MyGraph {
    type N = Node;
    type E = Edge;

    fn has_edge(&self, n1: &Node, n2: &Node) -> bool {
        true
    }

    fn edges(&self, n: &Node) -> Vec<Edge> {
        Vec::new()
    }
}Run

Это глупая реализация, которая всегда возвращает true и пустой Vec<Edge>, но она дает вам общее представление о том, как реализуются такие ​​вещи. Для начала нужны три struct, одна для графа, одна для узла и одна для ребра. В этой реализации используются struct для всех трех сущностей, но вполне могли бы использоваться и другие типы, которые работали бы так же хорошо, если бы реализация была более продвинутой.

Затем идет строка с impl, которая является такой же, как и при реализации любого другого типажа.

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

Типажи-объекты и ассоциированные типы

Вот еще немного синтаксиса, о котором следует упомянуть: типажи-объекты. Если вы попытаетесь создать типаж-объект из ассоциированного типа, как в этом примере:

let graph = MyGraph;
let obj = Box::new(graph) as Box<Graph>;Run

Вы получите две ошибки:

error: the value of the associated type `E` (from the trait `main::Graph`) must
be specified [E0191]
let obj = Box::new(graph) as Box<Graph>;
          ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~
24:44 error: the value of the associated type `N` (from the trait
`main::Graph`) must be specified [E0191]
let obj = Box::new(graph) as Box<Graph>;
          ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Мы не сможем создать типаж-объект, подобный этому, потому что у него нет информации об ассоциированных типах. Вместо этого, мы можем написать так:

let graph = MyGraph;
let obj = Box::new(graph) as Box<Graph<N=Node, E=Edge>>;Run

Синтаксис N=Node позволяет нам предоставлять конкретный тип, Node, для параметра типа N. То же самое и для E=Edge. Если бы мы не предоставляли это ограничение, то не могли бы знать наверняка, какая impl соответствует этому типажу-объекту.