Реализация итераторов в Rust вышла довольно удобная, да и сама коллекция доступных итераторов обширна и вполне сравнима с коллекцией итераторов из библиотеки BOOST.
{
let it = xs.iter(); // (1)
//xs.push(4); // (2)
}
Перед тем как говорить о деталях работы с итераторами, стоит сказать о крайне важном отличие итераторов Rust от итераторов в большинстве других языков программирования. Компилятор Rust, после создания итератора для коллекции
Благодаря такой функциональности, работа с итераторами в Rust выглядит куда как более безопасной по сравнению с другими языками, где итератор вполне может стать недействительным.
for val in data.iter() {
println(fmt!("Value: %?", val));
}
Самый простейший вариант использования итератора: обход всех элементов массива. Единственное что тут заслуживает внимания – это новое ключевое слово
for (pos, val) in data.iter().enumerate() {
println(fmt!("Value: %? with pos: %?", val, pos));
}
Чуть более сложный пример использования итератора – получение не только значения элемента, но и его порядкового номера. Стоит не забывать о том, что возвращаемое в качестве порядкового номера значение – это порядковый номер итерации, а не элемента в массиве.
let (lower, upper) = data.iter().size_hint();
println(fmt!("Lower bound: %?, upper bound: %?", upper, lower));
Так же у итератороа можно запросить количество доступных элементов. Так как реализация метода
let res1: ~[int] = data.iter().map(|&x| x * 2).collect(); // (1)
let res2 = data.iter().map(|&x| x * 2).collect::<~[int]>(); // (2)
//let res3: ~[uint] = data.iter().map(|&x| x * 2).collect(); // (3)
println(fmt!("res1: %?\nres2: %?", res1, res2));
Данные, полученные в результате итерации могут быть собранны в виде массива. Правда, тут есть одно несколько смущающее меня ограничение. Так, тип результат, например массив типа
Хочется верить, что это прост ошибка компилятора, а не особенность дизайна языка
let mut it = data.iter().map(|&val| 2 * val);
println(fmt!("transformed values: %?", it.collect::<~[int]>()));
println(fmt!("initial data: %?", data));
Само собой, классика функционального программирования не обошла нас стороной, и
Более детально об итераторах можно прочитать в заметке Containers and iterators на сайте языка или в документации по модулю iterator.
Упс… А сохранить итератор в какой-то структуре данных, получается, невозможно? В плюсах можно было, при желании сделать контейнер из итераторов. Не думаю, что на уровне компилятора такую штуку можно контролировать.
Знаешь, интересный момент. С одной стороны, я никогда не сохранял итераторы в массиве и с трудом представляю зачем это может быть нужно. С другой стороны, я сейчас покрутил компилятор и какой-либо способ создания массива итераторов мне в голову не пришел.
Тут же еще один интересный момент возникаем. Прототип функции возвращающей итератор для вектора выглядит следующим образом:
А итератор строки так:
И междуVecIterator и CharIterator нет ничего общего кроме того, что для обоих типов реализован трэйт Iterator , как следствие, разнородные итераторы ты тоже вместе хранить не сможешь. Но это уже совсем не проблема, на фоне того что массив итераторов, похоже что, не создашь
Итератор можно рассматривать как аналог указателя. Массив указателей, каждый элемент которого указывает на элемент из какого-то другого массива — это же для C/C++ вполне нормальное дело.
Пусть, например, у тебя есть список задач. Ты хочешь выбрать оттуда наиболее старые задачи и отослать каждой из них какое-то сообщение. Можно сделать сначала проход по списку и итераторы, указывающие на старые задачи собрать во вспомогательном векторе. А потом быстро пройтись по получившемуся вектору.
В случае с плюсовым list-ом, ЕМНИП, ты даже можешь использовать этот промежуточный вектор для удаления элементов из исходного list-а.
PS. А у тебя в блоге что, ответы на комментарии по почте не присылаются?
Но ведь хранение итератора создает дополнительные неудобства как минимум тем, что от может стать недействительным. И как следствие, всегда надо помнить о том, что у тебя где-то есть коллекция итераторов, которая может “неожиданно” стать не действительной. Мне кажется, концепция блокирования коллекции на изменение до тех пор, пока жив итератор коллекции, идея довольно верная, она, немного, ограничивает возможности, зато оберегает от ночей в отладчике.
Спасибо что сказал про ответы – письма о них действительно не отсылались, починил и со следующего твоего ответа у тебя все заработает
Как я говорил, итераторы можно рассматривать как указатели. В этом смысле инвалидация итератора такая же ожидаемая штука, как и инвалидация указателя.
Если итераторы применяются только для прохода по коллекции, тогда их полезность, имхо, значительно снижается.
Кроме того, не зря же существуют два типа итераторов: внешние (как в C++) и внутренние. Внутренние, помнится, раньше активно использовались в стандартной библиотеке Eiffel-я. Частично с внутренними итераторами разработчик сталкивается, когда вызывает операции map/inject/fold над коллекциями (например, в Ruby).
Вот когда внутренний итератор блокирует коллекцию, это мне больше понятно. Хотя здесь, имхо, на compile-time никаких гарантий дать нельзя, все проверки будут выполняться в run-time. Но вот блокировка коллекции внешними итераторами… Это неожиданная идея. Либо к ней нужно привыкнуть, либо она неправильная
Вообще, блокировка коллекции при наличии итератора для Rust логичный шаг. Все дело в отслеживании времени жизни переменных на этапе компиляции, которое имеет место в Rust. Собственно, компилятор об это и сказал: уже есть неизменяемый итератор и сделать еще один захват в качестве изменяемого никак нельзя. Если компилятор попытаться обхитрить, и захватить изменяемый итераторmut_iter() , то компилятор скажет что нельзя захватить итератор одного и того же типа дважды
Вообще, отслеживание времени жизни на этапе компиляции вызывает некоторые неудобства, но потенциальные плюсы, как мне кажется, перевешивают минусы.
Ну хорошо. Допустим есть задача пройти по коллекции, собрать ссылки на некоторые ее элементы в отдельном контейнере, затем пройти по этому новому контейнеру.
В новый контейнер собирать итераторы нельзя. Значит придется собирать ссылки на значения из коллекции. Но если мы сохраним в новом контейнере ссылки на значения, то кто нам помешает изменить потом исходную коллекцию? И, получить невалидные ссылки.
Компилятор не позволит тебе менять эти ссылки. Обрати внимание на следующий код:
{
let it = xs.mut_iter();
xs.push(4);
}
И сообщение компилятора не тему того, что он об этом коде думает:
Т.е. ты никак не сможешь провернуть такой операции. Это, безусловно, накладывает ограничения, но и от невалидных данных тебя спасает.
Мы с тобой, похоже, о разном говорим. Ты мне показываешь, что компилятор не дает изменить коллекцию, когда ты делаешь итерацию по ней. Это я понял. Но что, если в результате первой итерации ты должен сделать вторую коллекцию, состоящую из ссылок на элементы первой коллекции. После того, как ты закончишь первую итерацию, никаких итераторов на первую коллекцию у тебя не останется. Поэтому компилятор не запретит тебе модифицировать ее.
Так и коллекцию ссылок тебе компилятор создать не даст, в этом то и дело. Время жизни ссылки будет рассчитывать относительно времени жизни итератора, и как следствие, ссылку, которая живет дольше итератора, ты тоже не создашь.
Как-то все это неоднозначно. Нужно бы подождать впечатлений о тех, кто попользуется Rust-ом пару-тройку лет.
Да, я с тобой полностью согласен, у меня Rust тоже некоторые опасения вызывает, поглядим на развитие Servio. Но, на данный момент, это единственная потенциальная альтернатива C++ %)
согласен с Евгением.
блочить коллекцию пока есть (видимые?) итераторы как-то не нормально.
для тех же списков или мапы это совсем лишнее.
А какие есть еще идеи на тему того, как гарантировать безопасность операций с коллекцией на этапе компиляции? С учетом того, что обеспечение безопасности операций с памятью – одна из основных целей языка, шаг выглядит логичным.
P.S. ни разу за 12+ лет разработки на плюсах не хранил итераторы, передавал их куда-либо, делал по ним обход, но хранить. ХЗ, видимо авторы Rust тоже такой сценарий использования не рассматривают
А нужно ли гарантировать безопасность операций с коллекцией на этапе компиляции? Всех проблем все равно не прикроешь, а дополнительного головняка добавится.
Для упомянутых списков память при вставке не инвалидируется, так зачем ограничивать разработчика.
Итераторы хранить приходилось.
Последний раз это было когда понадобилась обертка над стандартным итератором с дополнительной функциональностью.
PS: за 12 лет часто тебе приходилось сталкиваться с проблемой с итераторами?
PPS: сам на эту граблю наступал искать было весело но считаю фичу лишней.
Гарантия безопасности операций с памятью – это одна из основных фич Rust. Поэтому, я думаю, что да, гарантировать безопасность операций с коллекциями надо.
Проблемы с итераторами бывали, хоть и не часто. В основном от непонимания когда итератор становится недействительным. Поэтому, меня фича только радует, чем меньше возможностей расстрелять память – тем лучше.