В чём причина недопустимости чтения элемента по endIndex у Collection?
endIndex — это позиция сразу после последнего элемента, а не индекс самого элемента. Поэтому обращение к коллекции по endIndex некорректно и обычно приводит к нарушению предусловия. Для обхода используют диапазон indices, а для получения последнего элемента — last или допустимый индекс перед endIndex.
Протокол Collection предоставляет единый интерфейс для разных структур данных: массивов, строк, множеств и пользовательских коллекций. У таких структур могут быть разные типы индексов и разная стоимость перемещения между ними, поэтому Swift не трактует индекс как универсальное целое число.
Модель полуоткрытого диапазона [startIndex, endIndex) позволяет единообразно описывать пустые и непустые коллекции. Она также делает endIndex удобным маркером завершения обхода, не требуя отдельного специального значения для пустой коллекции.
Если принять endIndex за индекс последнего элемента, можно получить ошибку при чтении или неверную логику обхода. Для пустой коллекции startIndex и endIndex совпадают, но элемента по этой позиции всё равно нет.
Нельзя безоговорочно заменять endIndex выражением «последняя позиция»: у некоторых коллекций индекс перед endIndex может быть вычислен только при поддержке двунаправленного обхода, а перемещение к нему может иметь ненулевую стоимость.
Индексы Collection образуют диапазон от startIndex включительно до endIndex исключительно. Валидными индексами элементов являются только позиции из collection.indices; endIndex служит границей, на которой обход должен остановиться.
В цикле проверяется именно принадлежность индекса диапазону indices, поэтому обращение безопасно. Свойство last возвращает последний элемент как optional: для пустой коллекции результатом будет nil.
Если коллекция поддерживает BidirectionalCollection, последний индекс можно получить через index(before: endIndex). Но этот вызов нельзя обобщённо применять к любой Collection: односторонняя коллекция не обязана уметь двигаться назад.
Важно отличать проверку индекса от проверки позиции по целому смещению. У String, например, индекс не обязан соответствовать номеру символа, поэтому корректные алгоритмы используют операции протокола Collection, а не арифметику целых чисел.
Допустим, обобщённая функция должна вывести элементы любой коллекции. Вариант с перебором целых чисел неприменим: у коллекции может не быть индексов типа Int, а вычисление позиции по смещению может быть дорогим.
Вариант с обращением к endIndex как к последнему элементу прост, но ошибочен: это пограничная позиция. Вариант с last удобен, если нужен только последний элемент, но не подходит для обхода всех элементов.
Выбранное решение — пройти по indices или использовать прямой for element in collection. Такой подход сохраняет универсальность, корректно обрабатывает пустую коллекцию и не делает лишних предположений о типе или устройстве индексов.
1. Может ли endIndex совпадать с индексом элемента?
Нет. По контракту endIndex обозначает позицию после последнего элемента. Даже если конкретная реализация внутренне использует целые числа, семантически это граница диапазона, а не доступный элемент.
**2. Всегда ли index(before: endIndex) даёт последний элемент?
Только для непустой коллекции, поддерживающей обратное перемещение. Для пустой коллекции такой вызов некорректен, поскольку перед endIndex нет индекса элемента. Поэтому сначала проверяют наличие элемента или используют last.
3. Почему нельзя считать endIndex числом количества элементов?
У Array внутренне это может выглядеть похоже, но общий протокол Collection не требует целочисленных индексов и не связывает индекс с количеством элементов. Например, индексы строки учитывают границы расширенных графем, поэтому их нельзя интерпретировать как обычные номера позиций.