


Присваивание среза в Go копирует его описание, а не все элементы базового массива. Два среза могут указывать на одни данные. slices.Clone из стандартного пакета slices создаёт копию элементов; пакет доступен начиная с Go 1.21. Это поверхностное копирование: вложенные срезы, карты и указатели требуют отдельного внимания.
В практическом разборе Mentoring.digital сначала изменим обычный срез чисел, затем проверим вложенные данные. Такой порядок помогает отличить независимость внешнего массива от глубокой копии всей структуры.
package main
import (
"fmt"
"slices"
)
func main() {
source := []int{10, 20, 30}
alias := source
alias[0] = 99
fmt.Println(source) // [99 20 30]
independent := slices.Clone(source)
independent[0] = 7
fmt.Println(source) // [99 20 30]
fmt.Println(independent) // [7 20 30]
}
Первая запись меняет общий массив. Во второй части Clone отделяет элементы, поэтому изменение числа в копии не отражается в source. Подсрез вроде source[:2] сам по себе тоже не копирует массив.
Не используйте append как способ гарантированно отделить данные. При наличии свободной ёмкости он способен использовать тот же базовый массив, а при её нехватке выделит другой. Поведение одной конкретной попытки не доказывает независимость срезов в остальных условиях.
rows := [][]int{{1, 2}, {3, 4}}
outerCopy := slices.Clone(rows)
outerCopy[0][0] = 9
fmt.Println(rows[0][0]) // 9
deepRows := make([][]int, len(rows))
for i, row := range rows {
deepRows[i] = slices.Clone(row)
}
deepRows[0][0] = 5
fmt.Println(rows[0][0]) // 9
Здесь скопированы элементы внешнего среза, а каждый элемент сам является описанием внутреннего среза. Поэтому внутренние массивы первоначально общие. Цикл создаёт отдельную копию каждой строки чисел. Для структуры с картами, указателями или несколькими уровнями вложенности нужно определить свой алгоритм копирования, а не считать этот цикл универсальным.
source := []int{10, 20, 30}
destination := make([]int, len(source))
n := copy(destination, source)
fmt.Println(n, destination) // 3 [10 20 30]
emptyLength := make([]int, 0, len(source))
fmt.Println(copy(emptyLength, source)) // 0
copy переносит столько элементов, сколько позволяет меньшая из длин источника и назначения. Одна ёмкость без длины не создаёт доступных элементов назначения. Это распространённая причина неожиданного нуля в результате.
slices.Clone сохраняет nil: копия nil-среза тоже равна nil. Если это часть вашего контракта сериализации или API, проверьте её явно. Не подменяйте проверку nil проверкой длины: и nil-срез, и инициализированный пустой срез могут иметь len == 0.
Создайте nil-срез, пустой срез, срез чисел и срез с вложенной строкой. После копирования измените внешний элемент, затем вложенный. Для каждого случая заранее сформулируйте, должен ли измениться источник. Если копирование нужно для конкурентного доступа, отдельно разберите владение вложенными данными: поверхностная копия не делает общую карту или указатель потокобезопасными.
Для освоения устройства данных подойдут курсы Go. Неочевидные зависимости между срезами можно разобрать с ментором по Go.