В чем особенность работы со ссылочными типами (слайсы, мапы, каналы) в Go?

Ответ

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

Это создает эффект передачи по ссылке:

  1. Общие базовые данные: Копируется только заголовок, а не все данные. Поэтому изменения, сделанные в функции через этот заголовок, отражаются на исходном объекте.

    func modifySlice(s []int) {
        // Эта функция получает копию заголовка слайса,
        // но он указывает на тот же самый базовый массив.
        s[0] = 100
    }
    
    func main() {
        originalSlice := []int{1, 2, 3}
        modifySlice(originalSlice)
        fmt.Println(originalSlice) // Вывод: [100 2 3]
    }
  2. Обязательная инициализация: Нулевое значение для ссылочных типов — nil. Попытка записи в неинициализированную мапу вызовет панику.

    var m map[string]int
    // m == nil
    m["key"] = 1 // panic: assignment to entry in nil map
    
    // Правильная инициализация
    m = make(map[string]int)
    m["key"] = 1 // OK
  3. Сравнение только с nil: Слайсы, мапы и каналы нельзя сравнивать друг с другом с помощью оператора ==. Их можно сравнивать только с nil.

  4. Нюанс с append: Если при добавлении элементов в слайс с помощью append его емкости (capacity) не хватает, Go создает новый базовый массив. Все последующие изменения будут применяться к этому новому массиву и не затронут исходный.

    func addElement(s []int) {
        // append может создать новый базовый массив
        s = append(s, 4)
        s[0] = 999 // Это изменение может не отразиться на оригинале
    }
    
    func main() {
        slice := make([]int, 3, 3) // len=3, cap=3
        slice[0] = 1
        addElement(slice)
        fmt.Println(slice) // Вывод: [1 0 0]. Изменения не видны.
    }

    Поэтому правильной практикой является всегда возвращать слайс из функции, которая его изменяет: s = addElement(s).