Сейчас для отслеживания настоящего момента удаления объекта из памяти у нас есть одна функция: runtime.SetFinalizer. Она говорит рантайму, что нас (через коллбэк) нужно уведомить о том, что объект был убран из памяти сборщиком мусора. Чаще всего это нужно, чтобы мы не забыли закрыть внешние дескрипторы (файлы/сеть/прочее), ассоциированные с объектом.
Однако у этой функции есть и ряд минусов:
‣ Она не является типобезопасной. Вот вам простой пример кода, который развалится только во время выполнения.
‣ Она допускает "воскрешение мёртвых объектов" (ситуации, когда через финализатор можно вернуть данные по удаляемому указателю обратно к жизни - пример кода). Эту штуку кстати ребята из TailScale использовали в своей либе go4.org/intern. У них еще крутая статья есть.
‣ Нельзя поставить несколько финализаторов на объект. Это здорово мешает, когда есть дополнительные вещи которые мы хотим выполнить при «смерти» объекта, а автор вызываемого кода уже повесил свой финализатор. Обходные пути есть, но у всех них есть свои тонкости.
В качестве решения предложена функция AddCleanup вот с такой сигнатурой:
func AddCleanup[T, S any](ptr *T, cleanup func(S), arg S) Cleanup
type Cleanup struct { ... }
// Stop cancels the cleanup call.
func (c Cleanup) Stop() { ... }
Суть в том, что мы вешаем коллбэк на указатель (
ptr), но в сам коллбэк приходит объект (arg) который мы ассоциировали с указателем.Что это нам дает?
‣ Больше нельзя указать неправильную сигнатуру для коллбэка.
‣ Идет разделение между тем, что мы отслеживаем и тем, что мы получаем в коллбэк при «смерти» отслеживаемого. Более того, сама функция пытается уберечь нас от ситуаций типо
arg == ptr (правда, только в рантайме).‣ Сначала выполняется финализатор (если он есть), а затем выполняется один или более коллбэк который мы повесили с помощью нового механизма.
А ждет нас это счастье в Go 1.24, ведь само изменение уже в мастере.
LRU Cache Example
На самом деле я немного покривил душой, когда говорил про LRU кеш: получаемый кеш имеет часть его свойств, но нельзя точно указать число объектов которые будут живы в памяти и время их жизни. Те из вас кто работал с другими языками скорее всего узнают WeakMap в самом примере.
Главное:
‣ Создаем словарь/мапу ключей к слабым указателям.
‣ Удаляем ключ как сработает AddCleanup.
На этом всё - у вас есть минимальный пример самоочищающегося кеша.