Когда нужно перенести элементы из одного слайса в другой, рука тянется к циклу с индексом. Это рабочий вариант, но многословный и легко ошибиться с границами. Для этой задачи в языке есть встроенная функция
copy.➡️ Что делает copy
Сигнатура:
func copy(dst, src []Type) int
Функция берёт элементы из источника
src и пишет их в dst. Возвращает число скопированных элементов. Это число всегда равно минимуму из длин двух слайсов:src := []int{1, 2, 3, 4}
dst := make([]int, 2)
n := copy(dst, src)
fmt.Println(n, dst) // 2 [1 2]Здесь у 'dst' длина 2, поэтому скопировались только первые два элемента, а остаток источника просто проигнорировался. Никакой ошибки или паники при этом нет.
➡️ Главное правило. Copy смотрит на длину, а не на capacity
Это та деталь, на которой спотыкаются чаще всего.
copy пишет ровно в те ячейки, что уже есть в приёмнике, то есть ориентируется на его длину. Если создать слайс с нулевой длиной, но большим запасом capacity, копировать будет некуда:dst := make([]int, 0, 10)
n := copy(dst, []int{1, 2, 3})
fmt.Println(n, dst) // 0 []
Длина нулевая, поэтому результат тоже ноль. Чтобы copy сработал, получатель должен иметь нужную длину заранее:
src := []int{1, 2, 3}
dst := make([]int, len(src))
copy(dst, src)
fmt.Println(dst) // [1 2 3]➡️ Как copy устроен внутри
Под капотом
copy это не цикл с поэлементным присваиванием, а перемещение блока памяти, по поведению близкое к memmove из языка C. Из этого следует важное свойство. Источник и приёмник могут пересекаться по памяти, и результат всё равно будет корректным. Наивное копирование в цикле в такой ситуации затёрло бы данные, которые ещё не успело прочитать, а copy справляется правильно:s := []int{1, 2, 3, 4, 5}
copy(s[1:], s[2:]) // сдвигаем хвост на одну позицию влево
fmt.Println(s) // [1 3 4 5 5]Именно поэтому
copy удобно использовать для сдвигов внутри одного слайса, например при удалении элемента:i := 2
copy(s[i:], s[i+1:])
s = s[:len(s)-1]
➡️ Особый случай со строкой
Строку можно копировать прямо в
[]byte, хотя строка формально не слайс. Это разрешённое исключение в языке. Берётся, как обычно, минимум из длин:dst := make([]byte, 5)
n := copy(dst, "hello world")
fmt.Println(n, string(dst)) // 5 hello
➡️ Copy копирует поверхностно
copy переносит сами значения элементов. Если элемент это указатель, слайс или мапа, в приёмник попадёт та же ссылка, а не отдельная копия вложенных данных. После копирования оба слайса будут указывать на одни и те же вложенные объекты, и изменение через один из них отразится на другом. Для глубокой копии вложенную структуру нужно клонировать отдельно.➡️ Когда брать copy, а когда нет
copy нужен, когда вы пишете в уже выделенный буфер, копируете только часть данных или сдвигаете элементы внутри слайса. Если задача в том, чтобы получить полную копию слайса целиком, в стандартной библиотеке есть slices.Clone. Это короткая обёртка над тем же make плюс copy, и она читается яснее:dst := slices.Clone(src)
copy берёт минимум из длин источника и приёмника, переносит элементы как блок памяти и возвращает число скопированных штук. Держите в голове два момента:
• результат зависит от длины приёмника, а не от его capacity, поэтому буфер задают через
make([]T, len(src)).• копия поверхностная, вложенные ссылочные данные остаются общими. С этими двумя правилами
copy становится простым и предсказуемым инструментом.📍 Навигация: Вакансии • Задачи • Собесы
🐸 Библиотека Go-разработчика
#GoToProduction