Почему присвоение элемента nil-карте вызывает панику в Go, но десериализация в ту же карту нет?

Вопрос или проблема

Инициализирует ли Unmarshal карты под капотом?

    var m map[string]int

    // m["hello"] = 1
    // Вывод: паника: присвоение в элемент nil карты

    err := json.Unmarshal([]byte(`{"hello": 1}`), &m)
    if err != nil {
        panic(err)
    }
    fmt.Println(m)
    // Вывод: map[hello:1]

Структуры, например, не работают тем же образом.

    type helloStruct struct {
        Hello int `json:"hello"`
    }
    var h *helloStruct
    err = json.Unmarshal([]byte(`{"hello": 1}`), h)
    fmt.Println(err)
    // Вывод: json: Unmarshal(nil *main.Hello)
    fmt.Println(h)
    // Вывод: <nil>

Карту в Go нужно инициализировать, прежде чем вы сможете её использовать. Когда вы объявляете var m map[string]int, карта по умолчанию равна nil.

используйте m := make(map[string]int)

Ответ или решение

В языке программирования Go существует строгая система управления памятью, которая требует, чтобы контейнеры, такие как карты, были инициализированы прежде чем использовать их. Ниже мы подробно обсудим, почему присвоение элементов в неинициализированную карту приводит к панике, в то время как разбор (Unmarshal) JSON в такую же карту не вызывает аналогичной ошибки.

Почему присвоение в nil-карту вызывает панику?

Когда вы объявляете карту в Go следующим образом:

var m map[string]int

Параметр m по умолчанию будет иметь значение nil. Это означает, что карта не была инициализирована, и любой доступ к ней (например, попытка присвоения значения) приведет к панике, как показано в следующем коде:

m["hello"] = 1 // вызывает панику: присвоение в элемент nil карты

Это происходит потому, что карта, не инициализированная с помощью make, не может хранить элементы — она именно что nil. Go использует механизм обработки ошибок, который включает паники для предотвращения непредсказуемых действий в коде. Когда вы пытаетесь записать значение в неинициализированную карту, Go не может выполнить это действие и вместо этого запускает панику.

Как Unmarshal обрабатывает карты?

Теперь давайте рассмотрим, как работает функция json.Unmarshal для карты. При применении следующего кода:

err := json.Unmarshal([]byte(`{"hello": 1}`), &m)

Поскольку m является указателем на nil карту (передача по указателю позволяет функции изменить оригинальное значение), функция Unmarshal в состоянии инициализировать m под капотом. При этом происходит следующее:

  1. Unmarshal проверяет, что параметр карты (указатель) m имеет значение nil.
  2. Функция вызывает make для инициализации карты.
  3. После инициализации, Unmarshal присваивает значения, как предусмотрено JSON.

Таким образом, в результате выполнения этого кода вы получаете корректно инициализированную карту:

fmt.Println(m) // вывод: map[hello:1]

Почему структуры ведут себя иначе?

С другой стороны, если вы работаете с указателем на структуру, например:

type helloStruct struct {
    Hello int `json:"hello"`
}
var h *helloStruct
err = json.Unmarshal([]byte(`{"hello": 1}`), h)

В этом случае код вызывает паническую ошибку с сообщением:

json: Unmarshal(nil *main.Hello)

Это связано с тем, что h также является nil, но в отличие от карт, структура не может быть инициализирована автоматически. В Go указатели на структуры должны быть инициализированы явно перед использованием. Для решения этой проблемы вы можете создать новую структуру перед разбором:

h = &helloStruct{}
err = json.Unmarshal([]byte(`{"hello": 1}`), h)

Заключение

В заключение, при работе с картами в Go важно помнить, что даже если карта объявлена, она может быть nil и требует инициализации. В отличие от операций с картами, JSON-разбор в Go для карт осуществляет инициализацию автоматически, в то время как для структур требуется явная инициализация указателей. Понимание этих принципов поможет избегать ошибок и эффективно использовать возможности языка Go для обработки данных.

Оцените материал
Добавить комментарий

Капча загружается...