Какие существуют способы создания (инициализации) слайса в Go и в чем их ключевые различия? (var, make, литерал)

Ответ

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

1. Объявление через var (нулевое значение)

Создает nil-слайс. Это не то же самое, что пустой слайс.

  • Синтаксис: var s []int
  • Состояние: s == nil, len(s) == 0, cap(s) == 0.
  • Память: Указатель на базовый массив равен nil. Память под элементы не выделяется.
  • Особенность: Несмотря на то что слайс nil, к нему можно безопасно применять функцию append. Go автоматически выделит память при первом добавлении элемента.

2. Использование литерала слайса

Создает слайс и сразу инициализирует его значениями.

  • Синтаксис:
    • s := []int{} — пустой слайс (не nil).
    • s := []int{1, 2, 3} — слайс с начальными элементами.
  • Состояние:
    • Для []int{}: s != nil, len(s) == 0, cap(s) == 0.
    • Для []int{1, 2, 3}: len(s) == 3, cap(s) == 3.
  • Память: Создается новый базовый массив, содержащий указанные элементы.

3. Использование функции make

Лучший способ, когда вы заранее знаете необходимый размер слайса. Это позволяет избежать многократных перераспределений памяти при использовании append, что повышает производительность.

  • Синтаксис:
    • make([]T, length) — создает слайс с длиной length и емкостью length.
    • make([]T, length, capacity) — создает слайс с длиной length и емкостью capacity.
  • Состояние (для make([]int, 3, 5)): len(s) == 3, cap(s) == 5. Слайс будет [0, 0, 0].
  • Память: Выделяется базовый массив размером capacity. Элементы инициализируются нулевыми значениями для их типа (0 для int, false для bool, и т.д.).

Сводный пример:

package main

import "fmt"

func main() {
    // 1. var (nil-слайс)
    var s1 []int
    fmt.Printf("s1: val=%v, is_nil=%t, len=%d, cap=%dn", s1, s1 == nil, len(s1), cap(s1))
    s1 = append(s1, 100)
    fmt.Printf("s1 after append: val=%v, is_nil=%t, len=%d, cap=%dnn", s1, s1 == nil, len(s1), cap(s1))

    // 2. Литерал (пустой, не nil)
    s2 := []int{}
    fmt.Printf("s2: val=%v, is_nil=%t, len=%d, cap=%dnn", s2, s2 == nil, len(s2), cap(s2))

    // 3. Литерал (с данными)
    s3 := []int{1, 2, 3}
    fmt.Printf("s3: val=%v, is_nil=%t, len=%d, cap=%dnn", s3, s3 == nil, len(s3), cap(s3))

    // 4. make
    s4 := make([]int, 3, 5)
    fmt.Printf("s4: val=%v, is_nil=%t, len=%d, cap=%dn", s4, s4 == nil, len(s4), cap(s4))
}

/*
Вывод:
s1: val=[], is_nil=true, len=0, cap=0
s1 after append: val=[100], is_nil=false, len=1, cap=1

s2: val=[], is_nil=false, len=0, cap=0

s3: val=[1 2 3], is_nil=false, len=3, cap=3

s4: val=[0 0 0], is_nil=false, len=3, cap=5
*/

Когда что использовать?

  • var s []T: Когда вы не знаете начальный размер и будете наполнять слайс динамически (например, с помощью append в цикле).
  • s := []T{...}: Когда вы заранее знаете точные значения, которые должны быть в слайсе.
  • make([]T, len, cap): Когда вы знаете необходимый размер или емкость слайса, но не его конкретные значения. Это самый производительный способ для таких случаев.