Массивы в Go являлись для меня одной из сложных тем, так как я не понимал как они работают. В данной статье рассмотрим как же именно работают слайсы и массивы в Go, а также как именно работает append и copy. Массивы - коллекция элементов одного типа. Длина массива не может изменяться. Вот как мы можем создать массив в Go: Если мы создадим два массива в Go с разными длинами, то два массива будут иметь разные типы, так как длина массива в Go, входит в его тип: Более того, если нам лень писать длину массива, то мы можем сказать компилятору, чтобы он сам подсчитал длину: Переменная, которую мы инициализировали со значением массива, содержит именно значения массива, а не ссылку на первый элемент массива (как это сделано в C). Именно поэтому массив в Go является примитивным типом данных, он может копироваться при передаче в другую переменную. По умолчанию в Go все значения копируются, а не передаются с помощью ссылки. Это значит, что если мы передадим наш массив в функцию, то Go скопирует данный массив и в функции будет находиться уже совершенно другой массив (вернее точная копия исходного массива). Внизу мы рассмотрим пример, где мы скопируем массив, а затем посмотрим на адрес, по которому хранится значение: Слайсы в Go более гибкие, они позволяют изменять свою длину. По сути слайсы являются надмножеством массивов. Слайсы создают нам массив, которым мы можем пользоваться как обычным массивом и при надобности расширяют его. Слайсы можно создать двумя способами: Способ с make является более интересным, так как дает нам возможность задать тип, длину и вместимость. С типом я думаю никаких проблем быть не должно. Тип слайса формируется в виде []тип. С длиной тоже ничего интересного. В зависимости от введенного количества - массив заполнится нулевыми значениями, например: Последний параметр - вместимость играет важную роль в производительности программы, а также является интересным. По сути он говорит о том сколько памяти нужно выделить заранее под наш массив, чтобы при расширении нам не пришлось искать новый участок памяти. Например, если мы создадим массив с вместимостью в 10 элементов, наполним его 5-ю элементами, а потом добавим один - адрес массива не изменится: К слову, если мы явно не задали вместимость слайса (то есть использовали конструкцию make([]int, 5)), то вместимость будет равна длине массива (в данном случае - 5). Если же мы укажем вместимость массива меньше, чем его длину, то код и вовсе нескомпилируется: Если же мы переполним вместимость слайса, то вместимость умножится на 2: При этом в памяти произойдет следующее: Go понимает что нам не хватает памяти и посмотрит есть ли после текущего сегмента памяти еще столько же ячеек; Если ячейки есть, он не будет передвигать массив и просто зарезервирует больше памяти; Если ячеек нет, то он скопирует всю информацию из уже использующегося сегмента и найдет вдвое больше свободных ячеек, после нажождения он перенесет туда все данные и отдаст нам адрес сегмента; С короткой версией объявления слайса все проще: В примере вверху Go создаст массив (под капотом) с длиной в три ячейки и такой же вместимостью. Срезом на слайсе является дочерний слайс, который ссылется только на часть слайса: Не смотря на то, что слайс и срез - понятия взаимозаменяемые (а если быть точнее, то срез - перевод от англ. slice), мы будем называть слайсами все новосозданные слайсы с помощью make() или shorthand-синтаксиса, а срезами будем называть слайсы проделанные над уже существующим массивом. Мы также можем делать срезы на массивах, таким образом мы можем делать массивы динамически расширяемыми: Слайс под капотом является структурой, которая содержит ссылку на исходный массив, длину и вместимость: Когда мы создаем новый слайс или срезаем массив, то ссылка массива присвивается полю array, с помощью данного указателя слайс сможет обращаться к массиву под капотом. length и capacity хранят длину и вместимость, соответственно. Поскольку слайс ссылается на часть массива, мы можем срезать часть массива. Срез не копирует элементы массива, он просто ссылается на них. Таким образом при изменении среза, изменится и массив, с которого мы брали срез: Мы также можем сделать так, чтобы срез занял всю длину исходного массива. Так как слайс хранит вместимость исходного массива - мы можем сделать срез снова и указать параметр cap(nameArray): У вас может возникнуть вопрос: почему мы не срезали cap(nameSlice) - 1, ибо мы указали в конце несуществующий индекс (на один больше, нежели существует в массиве). Все дело в том, что последний элемент при срезе не включается в срез. То есть, первый индекс идет включительно в срез, а последний - не включительно. Как уже можно понять, при срезе с массива или слайса мы не создаем новый слайс. Также, если мы присвоим одной переменной значение слайса другой переменной - они обе будут указывать на один массив: Мы можем избежать такого поведения с помощью копирования. Для того чтобы скопировать слайс (создать независимую копию) - нам достаточно использовать функцию copy: Мы можем заметить два различия: при использовании функции append - мы переприсваивали значение переменной: В случае с copy мы просто передаем саму переменную (не ссылку, а именно переменную!): Вот как работает копирование под капотом: Разработчики Go решили не добавлять часть  с инициализацией нового слайса внутрь copy. В случае с дополнением все немного иначе. Тут разработчики Go посчитали, что функция сама должна решать нужно ли инициализировать новый слайс, или можно дополнить данные в уже существующий слайс: Если вам понравилась данная статья - то вы всегда можете перейти в мой блог, там больше схожей информации о веб-разработке. Если у вас остались вопросы - не стесняйтесь задавать их в комментариях. Хорошего времяпрепровождения! 💁🏻‍♂  Frontend Engineer (Vue/Typescript) из Firecode