Можно ли навсегда избавится от утечек памяти из-за циклических ссылок?

uu8xawqpukxd_s6ew16e4ipcvqm.jpeg


В комментариях к статье статье в очередной раз услышал мнение, что раз невозможно принципе гарантировать отсутствие циклических ссылок при статическом анализе кода, то в языке Rust утечки памяти из-за циклических ссылок считаются безопасными, так как они не влияют на безопасность при работе с память.


Существует множество типов ошибок при работе с памятью, но единственные, которые до сих пор не имеют нормального решения, это утечки памяти из-за циклических ссылок, когда два или более объектов напрямую или косвенно ссылаются друг на друга, в результате чего доступная приложению оперативная память постепенно уменьшается, так как ее невозможно освободить автоматически.


Утечки памяти из-за циклических ссылок являются наиболее сложными для анализа, в то время как для всех остальные типов ошибок при работе с памятью уже найдены решения, например на уровне языка программирования, с помощью сборщиков мусора, проверки заимствований или использования шаблонов библиотек. Но проблема утечек памяти из-за циклических ссылок остается нерешенной и по сей день.


Но мне кажется, что есть очень простой способ решить проблему утечек памяти из-за циклических ссылок в программе, который можно реализовать практически в любом типизированном языке программирования, конечно, если при этом не использовать все разрешающее ключевое слово unsafe для Rust или std: reinterpret_cast в случае С++.


В чем изначальная проблема?


Чтобы понять, почему проблема циклических ссылок до сих пор не была решена, следует пояснить, откуда эта проблема вообще взялась.


Если говорить про серьезные языки программирования, которые предназначенные для разработки коммерческих приложений, то обязательным и безусловным требованием для них будет возможность повторения любой из уже существующих структур данных и алгоритмов, которые были придуманы и реализованы до этого ранее. И одной из таких базовых структур данных является связный список.


И так как каждый элемент связного списка должен хранить как минимум одну ссылку на точно такой же тип данных, вот тут и возникает необходимость наличия рекурсивных (циклических) ссылок, а вместе с ними и потенциальные утечки памяти.


А так как связные списки могут создаваться динамически (во время выполнения приложения), то в общем случае невозможно проанализировать логику возникновения зависимостей и доказать отсутствие циклических ссылок с помощью статического анализатора кода во время компиляции.


Как статически доказать отсутствие циклических ссылок в программе?


Если вспомнить теорию алгоритмов и структуры данных, то концепция рекурсивных ссылок у элемента на точно такой же тип данных используется очень-очень давно. Однако с той поры теория и практика языков программирования продвинулась очень далеко. Разработано множество концепций, с помощью которых удается решать многие проблемы автоматически, которые изначально приходилось программировать полностью вручную (из-за чего и происходит много ошибок).


Например, для автоматического освобождения ресурсов была придумана концепция RAII, для решения проблемы циклических ссылок были придуманы и успешно используются сильные и слабые ссылки.


Постойте, но раз проблема циклических ссылок решается с помощью сильных и слабых указателей, то почему эта проблема до сих пор существует в языках программирования?


Ведь проблему утечки памяти из-за циклических ссылок очень просто решить путем запрета на определение сильных рекурсивных ссылок в компиляторе на уровне типов (структур) данных.


Да, в этом случае классический связной список сделать не получится. Для связного списка в новой парадигме потребуется отдельный контейнер для хранения элементов списка с использованием сильных ссылок, так как самим элементам списка будет запрещено иметь сильные ссылки на свой собственный тип. Конечно, такая реализация связного списка потребуется немного больше памяти, но зато она в принципе исключит возможность создания циклических зависимостей!


Но самое главное заключается в том, что анализ типов на возможные циклические зависимости легко сделать статически во время компиляции исходного кода приложения. Тогда будет ненужно проводить анализ графа выполнения кода на предмет возникновения циклических ссылок, так как они будут запрещены компилятором на уровне типов (структур) данных!


При реализации такого подхода скорее всего будет нарушена обратная совместимость с некоторыми структурами данных, но зато при использовании сильных и слабых указателей в комбинация с концепцией RAII, сборщики мусора становятся вообще ненужными! А это может оказать очень полезным таких языков, как Java, Go или типизированного Python (а скорее всего можно будет отказать и от проверки заимствований).

© Habrahabr.ru