Здравствуйте, kaa.python, Вы писали:
ИД>>2. Заимствование в владение в масштабах всей системы. Заимствование хорошо работает "локально", но не очень "глобально" и наоборот. И ловушка тут в том, что как только заимствование начинает "заражать" структуры данных (грубо говоря, как в сигнатуре типа данных появляются переменные времени жизни) -- остановить это уже нетривиально.
KP>А вы не могли бы тут подробнее рассказать? Я вроде понимаю о чем речь, но можно немного в деталях и особенно что с этим делать?
Попробую объяснить на примере.
Был у нас
postgres::Connection. С ним всё хорошо -- можно класть в свои структуры данных и перемещать вместе с ними. Но нам нужна была транзакция,
postgres::transaction::Transaction. Так вот этот Transaction уже не столь удобен -- он "заимствует" из Connection и, соответственно, у него есть параметр времени жизни. Поэтому просто так положить его в какой-нибудь Arc и передать куда подальше уже не получится. Можно, но Arc будет привязан к этому параметру жизни, что в общем-то, убивает идею использования Arc (который часто используется для упрощения владением).
При этом пара Connection+Transaction внешних заимствований не имеет, то есть казалось бы, можно их вместе смотать изолентой и считать за одну сущность. Положить в одну структуру. Ан нет. Правила заимствования не позволят этого (это будет т.н "self-referential struct", с которыми в Rust туго).
Решения могут быть:
1. Взять что-нибудь типа
rental. Работает, эргономика так себе, безопасность 🤷🏻♂️. Собственно, эта та самая изолента и есть.
2. Переделать структуры данных, например, на использования Arc. Вот это тот самый случай, когда локальное решение ("хочу заимствовать vs хочу Arc") начинает влиять на более глобальные решения (на то, как данные и ссылки на них перетекают в рамках всей системы, в нашем случае нужен был некий "контекст" с активной транзакцией).
3. Какие-нибудь другие хаки с unsafe.
4. С появлением
std::pin, возможно, появились какие-то другие опции, но по-моему, пока нет.
В обратную сторону (когда у нас был Arc, а подсистема была заточена на заимствования) примеры тоже были, но не помню деталей. По-моему, что-то с параметризованным кодом, который от &A хочет перейти к &B, но если связка A->B идёт через Arc, то вот так просто позаимствовать уже не получится (этот параметризованный код должен будет знать про Arc).
Похожая связанная проблема, с которой мы столкнулись, -- traits vs trait objects.
Начинаешь делать статический полиморфизм, у тебя везде параметры типов начинают расползаться (что на большой кодовой базе приводит к раздуванию времени компиляции и размера кода, плюс типы начинают очень быстро выносить мозг) и заимствование не работает на 100% (в какой-то момент утыкаешься в отсутствие
GAT). Но при этом лучше абстрагируются структуры данных -- за trait проще спрятать всякую экзотику типа векторов с индексами.
Начинаешь делать динамический полиморфизм (trait objects), начинает более-менее работать заимствование, выглядит боле-менее естественно, но не всякую структуру данных можно спрятать за такой интерфейс (например, из вектора с индексами сложно что-то "заимствовать"). Утыкаешься либо в
Custom DSTs либо начинаешь делать лютые хаки типа
traitor.
По-хорошему, мне бы статью написать про всё это, да как-то времени нет :/