Ответ
Время жизни объекта в Go определяется не областью видимости (scope), а достижимостью (reachability). Объект живет до тех пор, пока на него существует хотя бы одна ссылка из корневого набора (глобальные переменные, активные стеки горутин).
Память в Go выделяется в двух местах: стек (stack) и куча (heap).
- Стек: Быстрая память для локальных переменных функции. Очищается автоматически при выходе из функции.
- Куча: Медленная память для данных, которые должны пережить вызов функции. Управляется сборщиком мусора (Garbage Collector, GC).
Ключевую роль в решении, где выделить память, играет анализ побега (escape analysis). Это процесс, который компилятор выполняет, чтобы определить, "сбегает" ли переменная из своей функции:
- Если переменная используется только внутри функции и не переживает её завершение (например,
x := 5
), она размещается на стеке. - Если на переменную есть ссылка, которая может быть использована после завершения функции (например, возвращается указатель на локальную переменную), переменная "сбегает" и размещается в куче.
// Возвращаемый указатель заставит 'x' "сбежать" в кучу.
func createObjectOnHeap() *int {
x := 42
return &x // x "сбегает"
}
// 'y' используется только внутри функции и останется на стеке.
func useStack() int {
y := 10
return y * 2 // y не "сбегает"
}
func main() {
p := createObjectOnHeap()
// Объект, на который указывает p, находится в куче.
// Он будет жить, пока p ссылается на него.
// Когда p выйдет из области видимости или станет nil,
// GC сможет освободить память.
val := useStack()
// Память под 'y' из useStack() уже освобождена.
}
Таким образом, escape analysis
позволяет Go оптимизировать производительность, размещая большинство объектов на быстром стеке и используя кучу и GC только при необходимости.