Функция получает массив фиксированной длины по значению. Видны ли вызывающему коду изменения его элементов внутри функции?
Нет. При передаче массива по значению функция получает независимую копию всего массива, поэтому изменения его элементов внутри функции не изменяют исходный массив вызывающего кода.
Чтобы изменить исходный массив, нужно передать указатель на него. Если размер коллекции не является частью её типа и требуется совместно изменять элементы, обычно используют срез: он передаётся по значению, но содержит ссылку на общий backing array.
Массив в Go — это значение фиксированного размера, а его длина входит в тип. Такое решение поддерживает простую и предсказуемую семантику копирования: присваивание и передача массива создают самостоятельное значение.
Для динамических коллекций в Go предназначен срез. Он отделяет представление коллекции от массива хранения и позволяет передавать небольшое значение-дескриптор вместо копирования всех элементов.
Ошибка возникает, когда разработчик ожидает от массива поведения среза. Функция может успешно изменить локальную копию, но вызывающий код продолжит видеть прежние данные.
Это особенно важно для больших массивов: передача по значению не только скрывает изменения, но и может привести к копированию всего массива. Неосторожный выбор типа способен повлиять и на корректность, и на производительность.
Массив содержит все свои элементы непосредственно. При передаче его параметру по значению создаётся копия, поэтому у функции и вызывающего кода разные экземпляры массива.
Чтобы изменить исходный массив, параметр должен иметь тип указателя на массив. Тогда функция работает с тем же объектом, но вызовы становятся теснее связаны с конкретным размером массива.
Срез ведёт себя иначе: копируется заголовок, обычно содержащий указатель на backing array, длину и вместимость. Поэтому изменение существующего элемента часто видно вызывающему коду, однако добавление элементов через append может создать новый backing array; изменение длины самого среза внутри функции при этом не меняет заголовок среза у вызывающего кода.
Выбор зависит от контракта. Массив подходит, когда размер фиксирован и копирование допустимо; указатель на массив — когда нужно изменять конкретный массив; срез — когда нужна работа с последовательностью переменной длины.
В обработчике передавали массив байтов фиксированного размера в функцию маскирования. Функция возвращала успешно, но отправляемый далее массив оставался немодифицированным: обработка выполнялась над копией.
Рассматривались два варианта. Передача указателя сохраняла фиксированный размер и исключала копирование, но делала API менее гибким. Замена массива на срез упрощала работу с буферами и позволяла обрабатывать диапазоны, однако требовала учитывать возможное перераспределение при append.
Выбрали срез, поскольку обработчику не требовался размер как часть типа и он работал с участками буфера. Изменения существующих байтов стали видны вызывающему коду, а операции добавления вынесли в отдельный контракт с возвратом обновлённого среза.
Вопрос: Копируется ли массив при присваивании одной переменной массива другой?
Ответ: Да. Присваивание массива копирует все его элементы, поэтому последующее изменение элемента через одну переменную не изменяет другую. Это отличается от присваивания срезов, при котором копируется заголовок, а backing array обычно остаётся общим.
Вопрос: Почему передача указателя на массив меняет семантику вызова?
Ответ: Значением параметра становится адрес массива, а не сам массив. Копируется только указатель, поэтому обращение к элементу через него изменяет исходный массив. При этом тип указателя содержит длину массива, например указатель на массив из трёх элементов нельзя напрямую заменить указателем на массив из четырёх.
Вопрос: Может ли функция, получившая срез, изменить длину среза у вызывающего кода?
Ответ: Нет, изменение длины меняет локальную копию заголовка среза. Однако функция может изменить элементы общего backing array, а если она выполняет append, он может вернуть новый срез с другой длиной и, при нехватке вместимости, с новым массивом хранения. Поэтому результат append нужно сохранять и обычно возвращать вызывающему коду.