Можно ли безопасно получить два изменяемых заимствования из разных частей одного среза, если граница разделения вычисляется во время выполнения?
Да. Для этого используют операцию split_at_mut, которая возвращает два изменяемых среза с непересекающимися диапазонами исходного среза. Компилятор не обязан самостоятельно доказывать непересечение произвольных индексов, но готовый безопасный API предоставляет такое доказательство через типы и проверку границы.
Модель владения Rust должна предотвращать одновременное существование конфликтующих доступов к одной памяти без сборщика мусора. Особенно опасно сочетание нескольких изменяемых ссылок: они могут указывать на один объект, что приводит к некорректным результатам, повреждению данных и гонкам при многопоточном доступе.
При этом практические программы часто обрабатывают независимые части одного массива или среза. Подход Rust сохраняет строгие правила, но позволяет вынести доказательство непересечения в специализированную абстракцию вместо ослабления правил заимствования.
Две изменяемые ссылки на один и тот же срез потенциально могут указывать на один элемент. Даже если программист видит, что индексы различаются, компилятор в общем случае не обязан доказывать это для произвольных вычислений.
Если разрешить такие ссылки без доказательства непересечения, один участок программы сможет изменить значение, пока другой участок использует ссылку на ту же память. Поэтому обычное индексирование не превращается автоматически в независимые изменяемые заимствования.
split_at_mut принимает границу и возвращает левую и правую части среза. Левая часть содержит элементы до границы, правая — элементы начиная с границы; один элемент не может принадлежать обеим частям.
Внутри реализация такого примитива может использовать unsafe, чтобы вручную создать два непересекающихся среза. Безопасность обеспечивается инвариантами самого API: граница проверяется, диапазоны не пересекаются, а вызывающий код получает только обычные безопасные ссылки.
После разделения left и right можно изменять независимо, потому что их типы содержат изменяемые заимствования разных частей исходного значения. Пока эти заимствования активны, исходный срез нельзя использовать для другого конфликтующего доступа.
Это отличается от владения двумя копиями массива: данные не дублируются, а остаются в исходном буфере. Компромисс состоит в том, что программист должен корректно выбрать границу и использовать API, способный выразить доказательство непересечения.
При обработке большого буфера нужно параллельно обновить левую и правую половины. Прямое получение двух изменяемых ссылок через индексы обычно не проходит проверку заимствований, поскольку компилятор не рассматривает разные вычисленные индексы как достаточное доказательство безопасности.
Можно скопировать элементы в два отдельных массива. Это упрощает заимствование, но требует дополнительной памяти и последующего объединения результатов. Можно написать собственный unsafe-код с указателями, однако тогда разработчик сам отвечает за проверку границ, непересечения и корректность времени жизни.
Предпочтительное решение — split_at_mut: оно не копирует данные, не требует unsafe в прикладном коде и предоставляет компилятору два независимых изменяемых среза. В результате сохраняются производительность исходного буфера и гарантии безопасной памяти.
Почему компилятор не может просто принять разные индексы за доказательство непересечения?
Потому что индексы могут быть равны после вычисления, зависеть от изменяемых данных или быть получены из сложной логики. Само наличие двух выражений с разными именами не гарантирует, что они обозначают разные элементы. split_at_mut превращает проверенное условие непересечения в структуру результата, которую уже можно безопасно использовать.
Что произойдёт, если граница разделения окажется равна нулю или длине среза?
Это безопасные случаи. При нулевой границе левая часть пуста, а правая содержит весь срез; при границе, равной длине, наоборот, пустой становится правая часть. Пустой срез не содержит элементов, поэтому не создаёт конфликта доступа с непустой частью.
Можно ли после вызова split_at_mut изменить размер исходного контейнера?
Нет, пока полученные срезы используются. Изменение размера контейнера может переместить его буфер в памяти или изменить расположение элементов, из-за чего существующие ссылки стали бы недействительными. Даже если конкретная операция фактически не вызовет перемещение, правило заимствования запрещает такой конфликт на уровне API и типов.