Изучая Go столкнулся с такой проблемой - не могу понять, как язык со статической типизацией поддерживает такие штуки, как рефлексия?
Именно на системном уровне.
Вот пример:
Нам прилетает неизвестный JSON. По идее мы должны создать структуру с полями, которые ожидаем в JSON, и только после этого распарсить.
НО
возможен вариант:
var f interface{}
err := json.Unmarshal(inputJSON, &f)
Магии, конечно, не бывает, но был бы благодарен, если кто-то объяснит.
Спасибо!
Go - язык с относительно слабой типизацией. С - слабая система типов. Haskell - строго типизирован полноценной системой Хиндли-Милнер. В С что угодно можно передать пойнтером и в этом смысле любой код можно писать generic но unsafe, reflection невозможен. В Haskell все типизируется на этапе компиляции. Typechecker Haskell - это полноценная Тьюринг Машина, любой код generic и safe при этом, поэтому reflection просто не нужен. Go - прагматичный компромисс, на этапе компиляции передаваемый тип может быть неизвестен но всегда известен на этапе выполнения. В Go, runtime всегда знает тип всех данных отсюда и за этим reflection.
Правильно ли я понимаю, мы создаем bar := interface{}. После создания тип bar
Когда же я передаю этот в Unmarshal(), то уже внутри него(внутри Unmarshal) runtime понимает что это Map, верно?
А вот дальше что? Этот интерфейс превращается в Map?
И еще, похоже я слабовато понимаю, что такое reflection. Получается, что при выполнении программы, со мной параллельно работает еще один поток? Или же все места, где необходим reflection определяется на этапе компиляции и далее просто происходит передача управления? Непонятен процесс определения.
Все верно. bar имеет тип interface{} для компилятора, но runtime знает что внутри Map. Эта информация может быть извлечена и использована, что и еть reflection. reflection не бесплатная, а одна из ресурсоемких операций, производительность заметно страдает. Там где в ваших силах спроектировать архитектуру с типами известными на этапе компиляции reflection следует по возможности избегать. Однако в случаях encoding или fmt.Println() reflection применяют для генерализации в ущерб производительности.
Наверное, я мыслю как сишник.
Начала читать про runtime. Правильно ли я понял, что это некая среда выполнения? То есть при запуске программы, там создается что-то похожее на JVM, которое управляет GC. И уже внутри этой среды, происходит reflection и другие "магические" штуки?
JVM исполняет байт код для вирт мпшины. Go компилируется в исполняемый код для конкретной архитектуры. runtime статически пристегивается компилятором к каждой Go программе. Функции runtime - диспетчеризация легких потоков по системным, сборка мусора и поддержка таблиц generic интерфейсов для конкретных типов где собственно и происходит reflection. Потоки диспетчиризуются отдельным процессом, сборка мусора несколькими отдельными конкурентными stop-the-world, интерфейсы reflection непосредственно в потоке исполнения. Я прям диссертацию пишу. Рад если смог быть полезен. Курите мануалы+))
В качестве самого хорошего примера сереализации советую рассмотреть ffjson. Обычно, рефлексией читают поля структуры и их аннотации и опосля кэширует их, ffjson напротив идёт путём кодогенерации. В некоторых случаях (с использованием интерфейсов в структурах) этот подход может быть ещё медленнее чем родной Marshal(), а для большей части прикладных задач, ещё и с использованием SIMD инструкций с SSE4 или SSE2 при работе со строками, получается сравнительно быстро. Ещё стоит глянуть исходники gob энкодера и декодера и почитать статью.