Go - это язык с автоматическим управлением памятью: сборщик мусора без участия программиста позаботится о том, чтобы освободить память, занимаемую объектами, более не используемыми программой. Но памятью вся автоматика в целом и ограничивается - об остальных используемых программой ресурсах нам по-прежнему нужно заботиться самостоятельно. Единственное, что может предложить сборщик мусора для автоматизации этого процесса - это при сборке объекта вызвать его финализатор, установленный с помощью функции runtime.SetFinalizer. Но этот механизм не гарантирует ни порядка вызова финализаторов, ни даже того, что финализатор вообще будет вызван. Это поведение не специфично для Go - подобным образом ведут себя все языки со сборкой мусора, поддерживающие финализацию, и, как следствие, полагаться на неё обычно не рекомендуется. Явное же освобождение ресурсов без специальных средств может быть утомительным и, что хуже - подверженным ошибкам. Собственно говоря, утечка памяти, являющаяся одной из разновидностей утечки ресурса - это одна из основных проблем, решаемых сборкой мусора. В коде типа очевидно слишком много мест, в которых можно допустить ошибку просто по невнимательности. И даже если ошибок допущено не будет, паника в любой из вызываемых функций сведёт все усилия на нет (в других языках аналогичную проблему создают исключения). А если функции Close ещё и возвращают ошибки, то дело становится совсем плохо - зачастую эти ошибки вообще игнорируются. Разные языки предлагают разные инструменты для упрощения освобождения ресурсов. Например, C# и Java предлагают using statement и try-with-resources statement соответственно. В Go же есть, как мне кажется, намного более гибкий defer statement. Код выше с его использованием становится на порядок удобнее и писать, и сопровождать: В этом коде уже есть гарантия вызова Close даже в случае паники. Хотя с обработкой ошибок Close дело по-прежнему обстоит плохо. Казалось бы, этого достаточно практически в любой ситуации. Но есть одно но - defer работает в пределах одной функции. Вы не сможете использовать код выше, например, в конструкторе - при выходе из него все созданные ресурсы будут освобождены, хотя они всё ещё требуются для работы программы. Конечно, можно возразить, что лучше бы в такой ситуации использовать внедрение зависимости. Однако, даже не считая того, что какие-то ресурсы всё ещё могут быть инкапсулированы, зависимости всё равно нужно где-то создать - а значит, мы должны либо объединить инициализацию с основной логикой в одной функции (увы, но нередко таковой является main), либо использовать инверсию управления, например, в форме колбэка, что в любом случае связывает инициализацию и основную логику сильнее, чем того хотелось бы. Наглядной демонстрацией описываемой проблемы является код, генерируемый Wire. Конструктор (провайдер в терминологии Wire) может вернуть т. н. cleanup function, которая будет использована для освобождения созданного им ресурса. Но код сгенерированного конструктора будет похож на первый сниппет в этой статье и в случае паники процесс освобождения ресурсов сломается. Тем не менее, cама по себе идея возврата cleanup function, предложенная Wire, на мой взгляд выглядит перспективной. Дело в том, что у метода Close (или аналогичного финализатора) есть сразу два недостатка: метод можно просто забыть вызвать; метод может быть по ошибке вызван зависимой от ресурса функцией. Если же финализатор возвращается не как часть ресурса, а как отдельная связанная с ним сущность, то второй недостаток нивелируется автоматически, поскольку у зависимой функции просто нет интерфейса для ошибочного вызова. Первый же недостаток нивелируется благодаря тому, что неиспользование объявленной переменной в Go является ошибкой времени компиляции: Итак, использование трёх выходных параметров (ресурс, финализатор и ошибка) вместо обычных двух (ресурс и ошибка) в типовой сигнатуре конструктора выглядит хорошим решением. В качестве бонуса, оно позволяет ввести концепцию "владения ресурсом", при которой ресурс принадлежит той сущности, которой известен его финализатор, а все остальные "заимствуют" или "арендуют" этот ресурс, не взаимодействуя с интерфейсом управления его жизненным циклом. Чтобы получить возможность использовать в конструкторе defer (вернее, максимально близкий к нему механизм), можно сделать следующее: Функция Finalize и типовая часть конструктора - отличные кандидаты для выноса в библиотеку и последующего переиспользования. Помимо возможности использовать привычный шаблон new - error - defer предложенный подход, при некоторой доработке, также предоставляет механизм для удобной обработки ошибок финализации, которые нередко игнорируются даже в примерах из официальной документации, поскольку их обработка оказывается весьма запутанной. Я опубликовал библиотеку KDone, предоставляющую описанный выше набор инструментов. Она является частью проекта Kata, о котором в следующий раз. Можно считать, что в данный момент её API стабилен и если и будет изменяться, то незначительно - тем не менее, библиотека пока свежая и я всё ещё использую нулевую мажорную версию на случай непредвиденных изменений. Типичный конструктор с использованием этой библиотеки выглядит так: Что думаете? Концепция достаточно простая, но возможно я всё же что-то упустил в своих рассуждениях? Или у вас есть предложения улучшений? Буду рад дискуссиям в комментариях. Программист