ВУЗ: Не указан
Категория: Не указан
Дисциплина: Не указана
Добавлен: 11.01.2024
Просмотров: 1159
Скачиваний: 5
ВНИМАНИЕ! Если данный файл нарушает Ваши авторские права, то обязательно сообщите нам.
СОДЕРЖАНИЕ
Обобщённые типы данных
Мы можем использовать обобщённые типы данных для функций или структур, которые затем можно использовать с различными конкретными типами данных. Давайте сначала посмотрим, как объявлять функции, структуры, перечисления и методы, используя обобщённые типы данных. Затем мы обсудим, как обобщённые типы данных влияют на производительность кода.
В объявлении функций
Когда мы объявляем функцию с обобщёнными типами, мы размещаем обобщённые типы в сигнатуре функции, где мы обычно указываем типы данных аргументов и возвращаемое значение. Используя обобщённые типы, мы делаем код более гибким, и предоставляем большую функциональность при вызове нашей функции, предотвращая дублирование кода.
Рассмотрим пример с функцией largest
. Листинг 10-4 показывает две функции, каждая из которых находит самое большое значение в срезе своего типа.
Файл: src/main.rs
Листинг 10-4: Две функции, отличающихся только именем и типом обрабатываемых данных
Функция largest_i32
уже встречалась нам: мы извлекли её в листинге 10-3, когда боролись с дублированием кода, она находит наибольшее значение типа i32
в срезе.
Функция largest_char находит самое большое значение типа char в срезе. Тело у этих функций одинаковое, поэтому давайте избавимся от дублируемого кода, добавив обобщённые типы данных.
Для параметризации типов данных в новой объявляемой функции, нам нужно дать имя обобщённому типу, также как мы это делаем для аргументов функций. Можно использовать любой идентификатор для имени параметра типа. Но мы будем использовать
T
, потому что, по соглашению, имена параметров в Rust должны быть короткими (обычно длиной в один символ) и именование типов в Rust делается в нотации CamelCase. Сокращение слова "type" до одной буквы
T
является стандартным выбором большинства программистов использующих язык Rust.
fn largest_i32
(list: &[
i32
]) -> &
i32
{ let mut largest = &list[
0
]; for item in list { if item > largest { largest = item;
}
} largest
} fn largest_char
(list: &[
char
]) -> &
char
{ let mut largest = &list[
0
]; for item in list { if item > largest { largest = item;
}
} largest
} fn main
() { let number_list = vec!
[
34
,
50
,
25
,
100
,
65
]; let result = largest_i32(&number_list); println!
(
"The largest number is {}"
, result); let char_list = vec!
[
'y'
,
'm'
,
'a'
,
'q'
]; let result = largest_char(&char_list); println!
(
"The largest char is {}"
, result);
}
Когда мы используем параметр в теле функции, мы должны объявить имя параметра в сигнатуре, так компилятор будет знать, что означает имя. Аналогично, когда мы используем имя параметра в сигнатуре функции, мы должны объявить имя параметра раньше, чем мы его используем. Чтобы определить обобщённую функцию largest
,
поместим объявление имён параметров в треугольные скобки,
<>
, между именем функции и списком параметров, как здесь:
Объявление читается так: функция largest является обобщённой по типу
T
. Эта функция имеет один параметр с именем list
, который является срезом значений с типом данных
T
. Функция largest возвращает данные такого же типа
T
Листинг 10-5 показывает определение функции largest с использованием обобщённых типов данных в её сигнатуре. Листинг также показывает, как мы можем вызвать функцию со срезом данных типа i32
или char
. Данный код пока не будет компилироваться, но мы исправим это к концу раздела.
Файл: src/main.rs
Листинг 10-5: определение функции
largest
с использованием обобщённых типов, но код пока не
компилируется
Если мы скомпилируем программу сейчас, мы получим следующую ошибку:
fn largest
0
]; for item in list { if item > largest { largest = item;
}
} largest
} fn main
() { let number_list = vec!
[
34
,
50
,
25
,
100
,
65
]; let result = largest(&number_list); println!
(
"The largest number is {}"
, result); let char_list = vec!
[
'y'
,
'm'
,
'a'
,
'q'
]; let result = largest(&char_list); println!
(
"The largest char is {}"
, result);
}
В подсказке упоминается std::cmp::PartialOrd
, который является типажом. Мы поговорим про типажи в следующей секции. Сейчас, ошибка в функции largest указывает, что функция не будет работать для всех возможных типов
T
. Так как мы хотим сравнивать значения типа
T
в теле функции, то можно использовать только те типы, данные которых можно упорядочить: можем упорядочить, значит можем и сравнить. Для возможности сравнения, стандартная библиотека имеет типаж std::cmp::PartialOrd
, который вы можете реализовать для типов (смотрите Дополнение
С для большей информации про данный типаж). Вы узнаете, как потребовать чтобы обобщённый тип реализовывал определённый типаж в секции "Типажи как параметры"
,
но сначала давайте рассмотрим другие варианты использования обобщённых типов.
1 ... 18 19 20 21 22 23 24 25 ... 62
В определении структур
Также можно определять структуры с использованием обобщённых типов в одном или нескольких полях структуры с помощью синтаксиса
<>
. Листинг 10-6 показывает как определить структуру
Point
, чтобы хранить поля координат x
и y
любого типа данных.
Файл: src/main.rs
Листинг 10-6: структура
Point
содержащая поля
x
и
y
типа
T
$
cargo run
Compiling chapter10 v0.1.0 (file:///projects/chapter10) error[E0369]: binary operation `>` cannot be applied to type `&T`
-->
src/main.rs:5:17
|
5 | if item > largest {
| ---- ^ ------- &T
| |
| &T
| help: consider restricting type parameter `T`
|
1 | fn largest
| ++++++++++++++++++++++
For more information about this error, try `rustc --explain E0369`. error: could not compile `chapter10` due to previous error struct
Point
} fn main
() { let integer = Point { x:
5
, y:
10
}; let float = Point { x:
1.0
, y:
4.0
};
}
Синтаксис использования обобщённых типов в определении структуры такой же как и в определении функции. Сначала мы объявляем имена параметров внутри треугольных скобок сразу после имени структуры. Затем мы можем использовать обобщённые типы в определении структуры на местах, где ранее мы бы указывали конкретные типы.
Так как мы используем только один обобщённый тип данных для определения структуры
Point
, это определение означает, что структура
Point
является обобщённой с типом
T
, и оба поля x
и y
имеют одинаковый тип, каким бы он типом не являлся. Если мы создадим экземпляр структуры
Point
со значениями разных типов, как показано в
Листинге 10-7, наш код не компилируется.
Файл: src/main.rs
Листинг 10-7: поля
x
и
y
должны быть одного типа, так как они имеют один и тот же обобщённый тип
T
В этом примере, когда мы присваиваем целочисленное значение 5 переменной x
, мы сообщаем компилятору, что обобщённый тип
T
будет целым числом для этого экземпляра
Point
. Затем, когда мы указываем значение 4.0 (имеющее тип отличный от целого числа) для y
, который мы определили имеющим тот же тип, что и x
, мы получим ошибку несоответствия типов:
Чтобы определить структуру
Point где оба x
и y
являются обобщёнными, но могут иметь различные типы, можно использовать несколько параметров обобщённого типа.
Например, в листинге 10-8 мы можем изменить определение
Point
, чтобы оно было общим для типов
T
и
U
где x
имеет тип
T
а y
имеет тип
U
Файл: src/main.rs struct
Point
} fn main
() { let wont_work = Point { x:
5
, y:
4.0
};
}
$
cargo run
Compiling chapter10 v0.1.0 (file:///projects/chapter10) error[E0308]: mismatched types
-->
src/main.rs:7:38
|
7 | let wont_work = Point { x: 5, y: 4.0 };
| ^^^ expected integer, found floating- point number
For more information about this error, try `rustc --explain E0308`. error: could not compile `chapter10` due to previous error
Листинг 10-8: структура
Point
обобщена для двух типов, так что
x
и
y
могут быть значениями
разных типов
Теперь разрешены все показанные экземпляры типа
Point
! В объявлении можно использовать столько много обобщённых параметров типа, сколько хочется, но использование более чем несколько типов делает код трудно читаемым. Когда вам нужно много обобщённых типов в коде, это может указывать на то, что ваш код нуждается в реструктуризации на более мелкие части.
В определениях перечислений
Как и в случае со структурами, можно определить перечисления для хранения обобщённых типов в их вариантах. Давайте ещё раз посмотрим на перечисление
Option
предоставленное стандартной библиотекой, которое мы использовали в
Главе 6:
Это определение теперь должно иметь больше смысла. Как видите, перечисление
Option
, которое является обобщённым по типу
T
и имеет два варианта:
Some
,
который содержит одно значение типа
T
и вариант
None
, который не содержит никакого значения. Используя перечисление
Option
, можно выразить абстрактную концепцию необязательного значения и так как
Option
является обобщённым,
можно использовать эту абстракцию независимо от того, каким будет тип для необязательного значения.
Перечисления также могут использовать в определении несколько обобщённых типов.
Определение перечисления
Result
, которое мы использовали в Главе 9, является таким примером:
struct
Point
} fn main
() { let both_integer = Point { x:
5
, y:
10
}; let both_float = Point { x:
1.0
, y:
4.0
}; let integer_and_float = Point { x:
5
, y:
4.0
};
} enum
Option
Some
(T),
None
,
}
Перечисление
Result имеет два обобщённых типа
T
и
E
и два варианта:
Ok
, которое содержит тип
T
, и
Err
, которое содержит тип
E
. Такое определение позволяет использовать перечисление
Result везде, где операции могут быть выполнены успешно (возвращая значение типа данных
T
) или неуспешно (возвращая значение типа данных
E
). Это то что мы делали в коде листинга 9-2, где при открытии файла заполнялись данные типа
T
, в примере тип std::fs::File или
E
тип std::io::Error при ошибке, при каких-либо проблемах открытия файла.
Когда вы в коде распознаете ситуации с несколькими структурами или определениями перечислений, которые отличаются только типами содержащих значений, вы можете избежать дублирования, используя обобщённые типы.
В определении методов
Также, как и в Главе 5, можно реализовать методы структур и перечислений с помощью обобщённых типов и их объявлений. Код листинга 10-9 демонстрирует пример добавления метода с названием x
в структуру
Point
, которую мы ранее описали в листинге 10-6.
Файл: src/main.rs
Листинг 10-9. Реализация метода с именем
x
у структуры
Point
, которая будет возвращать ссылку на поле
x
типа
T
Здесь мы определили метод с именем x
у
Point
который возвращает ссылку на данные в поле x
enum
Result
Ok
(T),
Err
(E),
} struct
Point
} impl
(&
self
) -> &T {
&
self
.x
}
} fn main
() { let p = Point { x:
5
, y:
10
}; println!
(
"p.x = {}"
, p.x());
}
Обратите внимание, что нужно объявить
T
сразу после impl
, чтобы можно было использовать его для указания, что мы реализуем методы для типа
Point
. Объявляя
T
как обобщённый тип после impl
, Rust может определить, что тип в угловых скобках у
Point
- это обобщённый, а не конкретный тип.
Мы могли бы, например, реализовать методы только для экземпляров типа
Point
вместо остальных экземпляров
Point
где используется какой-то другой обобщённый тип. В листинге 10-10 мы реализуем код для конкретного типа f32
: здесь мы не объявляем иных блоков impl для других вариантов обобщённого типа после.
Файл: src/main.rs
Листинг 10-10: блок
impl
который применяется только к структуре с конкретным типом для параметра
обобщённого типа
T
Этот код означает, что тип
Point
будет иметь метод с именем distance_from_origin
, а другие экземпляры
Point
где
T
имеет тип отличный от f32
не будут иметь этого метода. Метод измеряет, насколько далеко наша точка находится от точки с координатами (0,0, 0,0) и использует математические операции, доступные
только для типов с плавающей запятой.
Обобщённые типы в определении структуры не всегда являются теми же, которые вы используете в сигнатурах методов этой же структуры. Чтобы сделать пример более понятным, в листинге 10-11 используются обобщённые типы
X1
и
Y1
для структуры
Point и
X2
Y2
для метода mixup
. Метод создаёт новый
Point со значением x
из self
Point
(типа
X1
) и значением y
из другой
Point
(типа
Y2
), переданной в качестве параметра.
Файл: src/main.rs impl
Point<
f32
> { fn distance_from_origin
(&
self
) -> f32
{
(
self
.x.powi(
2
) + self
.y.powi(
2
)).sqrt()
}
}
Листинг 10-11: метод, использующий разные обобщённые типы из определения структуры для которой он
определён
В функции main
, мы определили тип
Point
, который имеет i32
для x
(со значением
5
) и тип f64
для y
(со значением
10.4
). Переменная p2
является структурой
Point которая имеет строковый срез для x
(со значением "Hello"
) и char для y
(со значением c
). Вызов mixup на p1
с аргументом p2
создаст для нас экземпляр структуры p3
. Новый экземпляр p3
будет иметь для x
тип i32
(потому что x
взят из p1
), а для y
тип char
(потому что y
взят из p2
). Вызов макроса println! выведет p3.x = 5, p3.y = c
Цель этого примера продемонстрировать ситуацию, в которой одни обобщённые параметры объявлены в impl
, а другие в определении метода. Здесь обобщённые параметры
X1
и
Y1
объявляются после impl
, потому что они идут вместе с определением структуры. Обобщённые параметры типа
X2
и
Y2
объявляются после fn mixup
, потому что они относятся только к методу.
Производительность кода использующего обобщённые типы
Вы могли бы задаться вопросом, появляются ли дополнительные вычисления во время выполнения кода использующего параметры обобщённого типа. Хорошей новостью является то, что Rust реализует обобщённые типы таким способом, что ваш код не работает медленнее при их использовании, чем если бы это было с конкретными типами.
struct
Point
} impl
self
, other: Point
Point { x: self
.x, y: other.y,
}
}
} fn main
() { let p1 = Point { x:
5
, y:
10.4
}; let p2 = Point { x:
"Hello"
, y:
'c'
}; let p3 = p1.mixup(p2); println!
(
"p3.x = {}, p3.y = {}"
, p3.x, p3.y);
}
Rust достигает этого благодаря выполнению мономорфизации кода использующего обобщения. Мономорфизация - это процесс превращения обобщённого кода в конкретный код во время компиляции, при котором из кода с обобщёнными типами генерируется код содержащий конкретные типы которые могут встретиться в вашем приложении.
В этом процессе компилятор выполняет противоположные шаги, которые обычно используются для создания обобщённой функции в листинге 10-5: компилятор просматривает все места, где вызывается обобщённый код и генерирует код для конкретных типов, с которыми вызван обобщённый код.
Давайте посмотрим, как это работает, на примере, который использует перечисление
Option
из стандартной библиотеки:
Когда Rust компилирует этот код, он выполняет мономорфизацию. Во время этого процесса компилятор считывает значения, которые были использованы у экземпляра
Option
и определяет два вида
Option
: один для i32
, а другой для f64
. Таким образом, он расширяет общее определение
Option
в
Option_i32
и
Option_f64
, тем самым заменяя обобщённое определение на конкретное.
Мономорфизированная версия кода выглядит следующим образом. Обобщённый
Option
заменяется конкретными определениями, созданными компилятором:
Файл: src/main.rs
Так как Rust компилирует обобщённый код, в код указывающий тип в каждом экземпляре, то мы не платим временем выполнения за использование обобщённых типов. Когда код выполняется, он работает так же, как если бы мы дублировали каждое определение вручную. Процесс мономорфизации делает обобщённые типы Rust чрезвычайно эффективными во время выполнения.
let integer =
Some
(
5
); let float =
Some
(
5.0
); enum
Option_i32
{
Some
(
i32
),
None
,
} enum
Option_f64
{
Some
(
f64
),
None
,
} fn main
() { let integer = Option_i32::
Some
(
5
); let float = Option_f64::
Some
(
5.0
);
}
Типажи: определение общего поведения
Типаж сообщает компилятору Rust о функциональности, которой обладает определённый тип и которой он может поделиться с другими типами. Можно использовать типажи, чтобы определять общее поведение абстрактным способом. Мы можем использовать ограничение типажа (trait bounds) чтобы указать, что общим типом может быть любой тип, который имеет определённое поведение.
Примечание: Типажи похожи на функциональность часто называемую
интерфейсами в других языках программирования, хотя и с некоторыми отличиями.
Определение типажа
Поведение типа определяется теми методами, которые мы можем вызвать у данного типа. Различные типы разделяют одинаковое поведение, если мы можем вызвать одни и те же методы у этих типов. Определение типажей - это способ сгруппировать сигнатуры методов вместе для того, чтобы описать общее поведение, необходимое для достижения определённой цели.
Например, пусть есть несколько структур, которые имеют различный тип и различный размер текста: структура
NewsArticle
, которая содержит которая содержит новость,
напечатанную в каком-то месте мира; структура
Tweet
, которая содержит 280
символьную строку твита и мета-данные, обозначающие является ли твит новым или ответом на другой твит.
Мы хотим создать крейт библиотеки медиа-агрегатора aggregator
, которая может отображать сводку данных сохранённых в экземплярах структур
NewsArticle или
Tweet
Чтобы этого достичь, нам необходимо иметь возможность для каждой структуры получить короткую сводку на основе имеющихся данных, и для этого мы запросим сводку вызвав метод summarize
. Листинг 10-12 показывает определение типажа
Summary
,
который выражает это поведение.
Файл: src/lib.rs
Листинг 10-12: Определение типажа
Summary
, который содержит поведение предоставленное методом
summarize
Здесь мы объявляем типаж с использованием ключевого слова trait
, а затем его название, которым в нашем случае является
Summary
. Также мы объявляем крейт как pub что позволяет крейтам, зависящим от нашего крейта, тоже использовать наш крейт,
pub trait
Summary
{ fn summarize
(&
self
) ->
String
;
}
что мы увидим в последующих примерах. Внутри фигурных скобок объявляются сигнатуры методов, которые описывают поведения типов, реализующих данный типаж, в данном случае поведение определяется только одной сигнатурой метода fn summarize(&self) -> String
После сигнатуры метода, вместо предоставления реализации в фигурных в скобках, мы используем точку с запятой. Каждый тип, реализующий данный типаж, должен предоставить своё собственное поведение для данного метода. Компилятор обеспечит,
что любой тип содержащий типаж
Summary
, будет также иметь и метод summarize объявленный с точно такой же сигнатурой.
Типаж может иметь несколько методов в описании его тела: сигнатуры методов перечисляются по одной на каждой строке и должны закачиваться символом
;
Реализация типажа у типа
Теперь, после того как мы определили желаемое поведение используя типаж
Summary
,
можно реализовать его у типов в нашем медиа-агрегаторе. Листинг 10-13 показывает реализацию типажа
Summary у структуры
NewsArticle
, которая использует для создания сводки в методе summarize заголовок, автора и место публикации статьи. Для структуры
Tweet мы определяем реализацию summarize используя имя пользователя и следующий за ним полный текст твита, полагая что содержание твита уже ограниченно 280
символами.
Файл: src/lib.rs
После сигнатуры метода, вместо предоставления реализации в фигурных в скобках, мы используем точку с запятой. Каждый тип, реализующий данный типаж, должен предоставить своё собственное поведение для данного метода. Компилятор обеспечит,
что любой тип содержащий типаж
Summary
, будет также иметь и метод summarize объявленный с точно такой же сигнатурой.
Типаж может иметь несколько методов в описании его тела: сигнатуры методов перечисляются по одной на каждой строке и должны закачиваться символом
;
Реализация типажа у типа
Теперь, после того как мы определили желаемое поведение используя типаж
Summary
,
можно реализовать его у типов в нашем медиа-агрегаторе. Листинг 10-13 показывает реализацию типажа
Summary у структуры
NewsArticle
, которая использует для создания сводки в методе summarize заголовок, автора и место публикации статьи. Для структуры
Tweet мы определяем реализацию summarize используя имя пользователя и следующий за ним полный текст твита, полагая что содержание твита уже ограниченно 280
символами.
Файл: src/lib.rs
Листинг 10-13: Реализация типажа
Summary
для структур
NewsArticle
и
Tweet
Реализация типажа у типа аналогична реализации обычных методов. Разница в том что после impl мы ставим имя типажа, который мы хотим реализовать, затем используем ключевое слово for
, а затем указываем имя типа, для которого мы хотим сделать реализацию типажа. Внутри блока impl мы помещаем сигнатуру метода объявленную в типаже. Вместо добавления точки с запятой в конце, после каждой сигнатуры используются фигурные скобки и тело метода заполняется конкретным поведением,
которое мы хотим получить у методов типажа для конкретного типа.
Теперь когда библиотека реализовала типаж
Summary для
NewsArticle и
Tweet
,
программисты использующие крейт могут вызывать методы типажа у экземпляров типов
NewsArticle и
Tweet точно так же как если бы это были обычные методы. Единственное отличие состоит в том, что программист должен ввести типаж в область видимости точно так же как и типы. Здесь пример того как бинарный крейт может использовать наш aggregator
:
pub struct
NewsArticle
{ pub headline:
String
, pub location:
String
, pub author:
String
, pub content:
String
,
} impl
Summary for
NewsArticle { fn summarize
(&
self
) ->
String
{ format!
(
"{}, by {} ({})"
, self
.headline, self
.author, self
.location)
}
} pub struct
Tweet
{ pub username:
String
, pub content:
String
, pub reply: bool
, pub retweet: bool
,
} impl
Summary for
Tweet { fn summarize
(&
self
) ->
String
{ format!
(
"{}: {}"
, self
.username, self
.content)
}
}
Данный код напечатает:
1 new tweet: horse_ebooks: of course, as you probably already know, people
Другие крейты, которые зависят от aggregator
, тоже могу включить типаж
Summary в
область видимости для реализации
Summary в их собственных типах. Одно ограничение,
на которое следует обратить внимание, заключается в том, что мы можем реализовать типаж для типа только в том случае, если хотя бы один из типажей типа является локальным для нашего крейта. Например, мы можем реализовать стандартный библиотечный типаж
Display на собственном типе
Tweet как часть функциональности нашего крейта aggregator потому что тип
Tweet является локальным для крейта aggregator
. Также мы можем реализовать
Summary для
Vec
в нашем крейте aggregator
, потому что типаж
Summary является локальным для нашего крейта aggregator
Но мы не можем реализовать внешние типажи для внешних типов. Например, мы не можем реализовать типаж
Display для
Vec
внутри нашего крейта aggregator
,
потому что
Display и
Vec
оба определены в стандартной библиотеке а не локально в нашем крейте aggregator
. Это ограничение является частью свойства называемого
согласованность (coherence), а ещё точнее сиротское правило (orphan rule), которое называется так потому что не представлен родительский тип. Это правило гарантирует,
что код других людей не может сломать ваш код и наоборот. Без этого правила два крейта могли бы реализовать один типаж для одинакового типа и Rust не сможет понять,
какой реализацией нужно пользоваться.
1 ... 19 20 21 22 23 24 25 26 ... 62
Реализация поведения по умолчанию
Иногда полезно иметь поведение по умолчанию для некоторых или всех методов в типаже вместо того, чтобы требовать реализации всех методов в каждом типе,
реализующим данный типаж. Затем, когда мы реализуем типаж для определённого типа,
можно сохранить или переопределить поведение каждого метода по умолчанию уже внутри типов.
use aggregator::{Summary, Tweet}; fn main
() { let tweet = Tweet { username:
String
::from(
"horse_ebooks"
), content:
String
::from(
"of course, as you probably already know, people"
,
), reply: false
, retweet: false
,
}; println!
(
"1 new tweet: {}"
, tweet.summarize());
}
В примере 10-14 показано, как указать строку по умолчанию для метода summarize из типажа
Summary вместо определения только сигнатуры метода, как мы сделали в примере 10-12.
Файл: src/lib.rs
Листинг 10-14: Определение типажа
Summary
с реализацией метода
summarize
по умолчанию
Для использования реализации по умолчанию при создании сводки у экземпляров
NewsArticle вместо определения пользовательской реализации, мы указываем пустой блок impl с impl Summary for NewsArticle {}
Хотя мы больше не определяем метод summarize непосредственно в
NewsArticle
, мы предоставили реализацию по умолчанию и указали, что
NewsArticle реализует типаж
Summary
. В результате мы всё ещё можем вызвать метод summarize у экземпляра
NewsArticle
, например так:
Этот код печатает
New article available! (Read more...)
Создание реализации по умолчанию не требует от нас изменений чего-либо в реализации
Summary для
Tweet в листинге 10-13. Причина заключается в том, что синтаксис для переопределения реализации по умолчанию является таким же, как синтаксис для реализации метода типажа, который не имеет реализации по умолчанию.
Реализации по умолчанию могут вызывать другие методы в том же типаже, даже если эти другие методы не имеют реализации по умолчанию. Таким образом, типаж может предоставить много полезной функциональности и только требует от разработчиков указывать небольшую его часть. Например, мы могли бы определить типаж
Summary имеющий метод summarize_author
, реализация которого требуется, а затем определить метод summarize который имеет реализацию по умолчанию, которая внутри вызывает метод summarize_author
:
pub trait
Summary
{ fn summarize
(&
self
) ->
String
{
String
::from(
"(Read more...)"
)
}
} let article = NewsArticle { headline:
String
::from(
"Penguins win the Stanley Cup Championship!"
), location:
String
::from(
"Pittsburgh, PA, USA"
), author:
String
::from(
"Iceburgh"
), content:
String
::from(
"The Pittsburgh Penguins once again are the best \ hockey team in the NHL."
,
),
}; println!
(
"New article available! {}"
, article.summarize());
Чтобы использовать такую версию типажа
Summary
, нужно только определить метод summarize_author
, при реализации типажа для типа:
После того, как мы определим summarize_author
, можно вызвать summarize для экземпляров структуры
Tweet и реализация по умолчанию метода summarize будет вызывать определение summarize_author которое мы уже предоставили. Так как мы реализовали метод summarize_author типажа
Summary
, то типаж даёт нам поведение метода summarize без необходимости писать код.
Этот код печатает
1 new tweet: (Read more from @horse_ebooks...)
Обратите внимание, что невозможно вызвать реализацию по умолчанию из переопределённой реализации того же метода.
Типажи как параметры
Теперь, когда вы знаете, как определять и реализовывать типажи, можно изучить, как использовать типажи, чтобы определить функции, которые принимают много различных типов. Мы будем использовать типаж
Summary
, реализованный для типов
NewsArticle и
Tweet в листинге 10-13, чтобы определить функцию notify
, которая вызывает метод summarize для его параметра item
, который имеет некоторый тип, реализующий типаж
Summary
. Для этого мы используем синтаксис impl Trait примерно так:
pub trait
Summary
{ fn summarize_author
(&
self
) ->
String
; fn summarize
(&
self
) ->
String
{ format!
(
"(Read more from {}...)"
, self
.summarize_author())
}
} impl
Summary for
Tweet { fn summarize_author
(&
self
) ->
String
{ format!
(
"@{}"
, self
.username)
}
} let tweet = Tweet { username:
String
::from(
"horse_ebooks"
), content:
String
::from(
"of course, as you probably already know, people"
,
), reply: false
, retweet: false
,
}; println!
(
"1 new tweet: {}"
, tweet.summarize());
Вместо конкретного типа у параметра item указывается ключевое слово impl и имя типажа. Этот параметр принимает любой тип, который реализует указанный типаж. В
теле notify мы можем вызывать любые методы у экземпляра item
, которые приходят с типажом
Summary
, такие как метод summarize
. Мы можем вызвать notify и передать в него любой экземпляр
NewsArticle или
Tweet
. Код, который вызывает данную функцию с любым другим типом, таким как
String или i32
, не будет компилироваться, потому что эти типы не реализуют типаж
Summary
Синтаксис ограничения типажа
Синтаксис impl Trait работает для простых случаев, но на самом деле является синтаксическим сахаром для более длинной формы, которая называется ограничением
типажа (trait bound); это выглядит так:
Эта более длинная форма эквивалентна примеру в предыдущем разделе, но она более многословна. Мы помещаем объявление параметра обобщённого типа с ограничением типажа после двоеточия внутри угловых скобок.
Синтаксис impl Trait удобен и делает код более сжатым в простых случаях, в то время как более полный синтаксис с ограничением типажа в других случаях может выразить большую сложность. Например, у нас может быть два параметра, которые реализуют типаж
Summary
. Использование синтаксиса impl Trait выглядит так:
Использовать impl Trait удобнее если мы хотим разрешить функции иметь разные типы для item1
и item2
(но оба типа должны реализовывать
Summary
). Если же мы хотим заставить оба параметра иметь один и тот же тип, то мы должны использовать ограничение типажа так:
Обобщённый тип
T
указан для типов параметров item1
и item2
и ограничивает функцию так, что конкретные значения типов переданные аргументами для item1
и item2
должны быть одинаковыми.
Задание нескольких границ типажей с помощью синтаксиса +
pub fn notify
(item: &
impl
Summary) { println!
(
"Breaking news! {}"
, item.summarize());
} pub fn notify
(
"Breaking news! {}"
, item.summarize());
} pub fn notify
(item1: &
impl
Summary, item2: &
impl
Summary) { pub fn notify