Как устроено копирование структуры, содержащей срез?
При присваивании структуры копируются значения всех её полей. Для поля типа срез копируется только заголовок среза — ссылка на массив, длина и вместимость, поэтому исходная и скопированная структуры обычно используют один массив данных.
Изменение элемента через один срез видно через другой. Но изменение самого заголовка среза — например, присваивание нового среза или изменение длины — затрагивает только ту структуру, в которой оно выполнено.
Go сочетает строгую семантику значений с эффективными дескрипторами для составных данных. Это позволяет копировать структуры без неявного глубокого копирования больших массивов, сохраняя предсказуемое поведение обычного присваивания.
Срез специально представлен небольшим дескриптором, а не самим массивом. Такой подход уменьшает стоимость передачи и копирования срезов, но требует учитывать совместное владение базовым массивом.
Разработчик может ожидать, что копирование структуры создаст полностью независимую копию. Для структуры со срезом это неверно: запись элемента через одну копию может изменить данные, наблюдаемые через другую.
Обратная ошибка тоже возможна: изменение длины или переназначение среза в одной структуре не изменяет поле среза в другой. Из-за этого код может одновременно иметь общие элементы и независимые заголовки, что особенно опасно при передаче структур между конкурентными горутинами.
При копировании структуры Go копирует каждое поле согласно его обычной семантике. Поле-массив копируется целиком, а поле-срез копируется как дескриптор из указателя на базовый массив, длины и вместимости.
В этом примере изменение b.Data[0] видно через a.Data, потому что элементы находятся в общем базовом массиве. Результат append зависит от доступной вместимости: если места достаточно, новый элемент попадёт в общий массив; если вместимости не хватает, будет создан новый массив, и дальнейшие изменения b.Data уже не будут затрагивать a.Data.
Само присваивание b.Data = ... меняет только поле Data структуры b. Чтобы получить независимые элементы, нужно явно скопировать их в новый срез, например с помощью make и copy.
Похожие свойства имеют поля-указатели, map и каналы: копируется значение, указывающее на общую сущность. Напротив, вложенная структура без ссылочных полей копируется рекурсивно как значение.
При наличии конкурентного доступа совместный базовый массив требует синхронизации. Копирование структуры не создаёт защиты от гонок и не является способом сделать данные потокобезопасными.
Сервис хранит структуру запроса с полем-срезом и передаёт её в несколько обработчиков. Один обработчик только читает данные, а другой нормализует элементы на месте. Несмотря на копирование самой структуры, обработчики видят один базовый массив, поэтому изменение одного обработчика влияет на результаты другого и может вызвать гонку.
Вариант с передачей структуры как есть сохраняет низкие накладные расходы, но не обеспечивает изоляцию. Вариант с глубоким копированием даёт независимые данные, однако требует дополнительной памяти и времени, причём стоимость зависит от размера среза.
Практичное решение — явно определить владение данными: если обработчику нужна модифицируемая копия, создать новый срез и скопировать элементы; если данные только читаются, передавать общий срез и не изменять его. В результате границы владения становятся явными, а риск скрытых изменений и гонок уменьшается.
Нет. Это верно только пока обе копии ссылаются на один базовый массив и изменяется существующий элемент в пределах длины среза. После append, который выделил новый массив, копии могут стать независимыми по данным.
Нет. Длина входит в заголовок среза, а заголовок копируется отдельно. Поэтому операция изменения длины или присваивание нового среза меняет только конкретное поле конкретной структуры, хотя элементы до этого могли оставаться общими.
Нет. Копирование структуры со срезом, map или указателем может сохранить общий доступ к изменяемым данным. Для безопасной передачи нужно либо обеспечить неизменяемость, либо синхронизировать общий объект, либо выполнить необходимое глубокое копирование.