Нужно описать итератор, который возвращает ссылку на данные внутри самого итератора. Какую роль играет параметр 'a у ассоциированного типа Item?
trait LendingIterator {
type Item<'a>
where
Self: 'a;
fn next<'a>(&'a mut self) -> Option<Self::Item<'a>>;
}
struct One {
value: String,
yielded: bool,
}
Item<'a> — это обобщённый ассоциированный тип: конкретная реализация трейта задаёт семейство типов, зависящих от времени жизни 'a. Это позволяет возвращать ссылку, заимствованную из self, с той же временем жизни, что и заимствование &'a mut self.
Без такой зависимости ассоциированный тип был бы одним фиксированным типом для всей реализации и не мог бы корректно описать результат, срок жизни которого определяется конкретным вызовом метода.
Обычные ассоциированные типы позволяют реализации трейта выбрать один тип результата, например type Item = u32. Однако этого недостаточно для API, где возвращаемое значение заимствует данные из объекта и должно быть связано с конкретным заимствованием.
Generic associated types, или GAT, появились как средство выразить такие зависимости непосредственно в трейтах: ассоциированный тип может принимать параметры типов или времён жизни. Это решает проблему описания «заимствующих» итераторов, представлений данных и других API без необходимости возвращать принадлежащие значения или использовать динамическую диспетчеризацию.
Предположим, итератор возвращает окно или элемент из собственного буфера. Результат должен оставаться действительным только пока активно заимствование итератора, иначе API позволил бы получить ссылку с некорректным временем жизни.
Обычный Iterator использует один Item для всей реализации. Он хорошо подходит для принадлежащих значений, но не выражает зависимость результата от времени жизни конкретного вызова next. Попытка заменить ссылку принадлежащим значением может привести к лишним копированиям, выделениям памяти или изменению семантики API.
В объявлении type Item<'a> задаётся не один тип, а функция от времени жизни: для каждого допустимого 'a реализация предоставляет соответствующий тип. В данном случае реализация может определить Item<'a> как &'a str.
Параметр метода &'a mut self означает, что результат может заимствовать данные из объекта на время 'a. Связь сохраняется системой типов: пока вызывающий код использует возвращённую ссылку, соответствующее изменяемое заимствование итератора не может быть нарушено новым вызовом next.
Ограничение where Self: 'a утверждает, что Self живёт не меньше 'a. Оно необходимо, поскольку ассоциированный тип может содержать ссылку, связанную с самим Self; без этого компилятор не мог бы считать такое определение корректно сформированным во всех случаях.
Это отличается от обычного Iterator<Item = &'static str>: 'static не означает «ссылка на данные внутри текущего итератора», а требует, чтобы данные жили всю статическую жизнь программы. GAT позволяет использовать именно локальное время жизни вызова.
Главный компромисс — более сложные ограничения заимствований. Если вызывающий код хранит возвращённую ссылку, он обычно не сможет сразу снова вызвать next, потому что для этого потребуется новое изменяемое заимствование того же объекта. Это корректное ограничение: иначе старую ссылку можно было бы использовать после изменения или перемещения связанных данных.
GAT не делают тип автоматически пригодным для трейтового объекта и не включают динамическую диспетчеризацию. Они описывают зависимость типов на этапе компиляции; вызовы через обобщённый параметр обычно остаются статически диспетчеризуемыми.
В парсере нужно возвращать срез текущего входного буфера без копирования. Рассматривались три варианта.
String или Vec<u8>. API проще, но каждый результат владеет данными и может требовать копирования или выделения памяти.Iterator<Item = &str>. Такой интерфейс не может выразить, что время жизни ссылки зависит от конкретного заимствования итератора.Выбирается третий вариант, если входной буфер велик, элементы краткоживущие, а копирование заметно влияет на производительность. Если же элементы должны свободно сохраняться после продвижения итератора, лучше вернуть принадлежащие значения: GAT не отменяет правила владения и не делает ссылки независимыми от источника.
1. Чем GAT отличается от параметра типа самого трейта?
Параметр типа трейта задаётся при выборе реализации или использовании трейта, например Trait<T>. GAT задаёт тип, который может меняться для каждого параметра 'a внутри одной реализации. Это важно для результата, зависящего от конкретного заимствования, а не от единого параметра всей абстракции.
2. Почему после получения ссылки нельзя безусловно вызвать next ещё раз?
Результат может содержать ссылку на данные внутри self, а next принимает &'a mut self. Пока ссылка используется, активное заимствование защищает эти данные от потенциального изменения. Компилятор запрещает повторный вызов не из-за GAT как такового, а из-за обычных правил эксклюзивного изменяемого заимствования.
3. Можно ли заменить GAT на Box<dyn Trait> и сохранить те же свойства?
Трейтовый объект может скрыть конкретный тип реализации, но сам по себе не выражает произвольную зависимость результата от времени жизни заимствования. Кроме того, динамическая диспетчеризация добавляет косвенный вызов и часто требует выделения памяти для объектов. Если набор типов известен на этапе компиляции и важны нулевые накладные расходы, GAT с обобщённым кодом обычно лучше; если важнее единый гетерогенный контейнер, динамическая модель может быть оправдана.