Немного о пустых интерфейсах. Быстрый взгляд изнутри
Warning: статья не принесет ничего нового для профи, но будет полезна новичкам.
Если вы это читаете, значит я уже мертв вы, как минимум, интересуетесь языком Go. Следовательно, знаете, о такой вещи, как interface{}.
А что будет, если я скажу, что любой интерфейс это просто структура? А также, что довольно легко реализовать самому свои интерфейсы? Прошу под кат.
Давайте начнем с предельно простого. Напишем простенькую программу на Go:
package main
import "fmt"
func ifacePrint(a interface{}) {
fmt.Println(a)
}
func main() {
ifacePrint("Habrahabr")
}
Всё просто — main, который вызывает метод ifacePrint и собственно ifacePrint, который явно принимает один аргумент типа interface{}.
Если мы её запустим, то получим вывод строки «Habrahabr». Но нам это не интересно,
поэтому мы запустим нашу программку под надзором gdb.
И так, go build main.go && gdb ./main.
Ставим breakpoint на наш метод:
(gdb) b main.ifacePrint
Breakpoint 1 at 0x401000: file /tmp/main.go, line 5.
И запускаем программу:
(gdb) run
Starting program: /tmp/main
[New LWP 4892]
[New LWP 4893]
[New LWP 4894]
[New LWP 4895]
Breakpoint 1, main.ifacePrint (a=...) at /tmp/main.go:5
5 func ifacePrint(a interface{}) {
gdb тут же останавливает выполнение, дойдя до бряка.
Давайте посмотрим, какого типа у нас переменная а:
(gdb) whatis a
type = interface {}
Что ж, логично, interface{}, ну, заглянем, тогда, что ли в её содержимое?
(gdb) p a
$1 = {_type = 0x4b8e00, data = 0xc8200761b0}
Интересный поворот, мы же должны были отправить строку…
Однако, это и есть наша строка, точнее её interface, в поле data лежит указатель, на область в памяти, в которой и содержится наша строка, а _type — указатель на внутренюю (рантайма) структуру TypeDescriptor.
Забегая вперёд и в сторону покажу, как я делал интерфейсы для своей ОС на Go (gccgo):
//Реализация пустого интерфейса
type EmptyInterface struct {
__type_descriptor *TypeDescriptor // Поинтер на структуру - описание типа
__object uintptr // Поинтер на данные
}
// Структура - описание типа
type TypeDescriptor struct {
kind uint8 //Код типа данных, можно считать за ID
align uint8 //Выравнивание в памяти
fieldAlign uint8 //Выравнивание свойств (если есть)
size uintptr //Размер
hash uint32
hashfn uint32 //TODO
equalfn uint32 //TODO
gc uintptr //TODO
string *string //Строковое описание типа ("string", "int")
uncommonType *Uncommon //Дополнительное описание для не встроеных типов
ptrToThis *TypeDescriptor // Описание указателя на данный тип
}
//Дополнительное описание для пользовательских типов
type Uncommon struct {
name *string //Имя типа данных
pkgPath *string //Имя пакета, в котором описан
methods uintptr //TODO Указатель на массив методов, в которых ресивером является данный тип данных
}
В «классическом» Go — всё примерно также: (gdb) p *a._type
$7 = {size = 16, ptrdata = 8, hash = 3774831796, _unused = 0 '\000', align = 8 '\b', fieldalign = 8 '\b', kind = 24 '\030',
alg = 0x582860
gcdata = 0x52a3ac "\001\002\003\004\005\006\a\b\t\n\r\016\017\020\022\025\026\031\032\033\037,568
Здесь мы видим примерно всю ту же информацию о типа данных «скрывающемся» за пустым интерфейсом.(gdb) p a._type._string
$11 = (struct string *) 0x50e5e0
(gdb) p *a._type._string
$12 = 0x4fdfb0 "string"
(gdb) p *a._type.x
$13 = {name = 0x50e5e0, pkgpath = 0x0, mhdr = {array = 0x4b8e68, len = 0, cap = 0}}
(gdb) p *a._type.x.name
$14 = 0x4fdfb0 "string"
(gdb) p *a._type.ptrto
$15 = {size = 8, ptrdata = 8, hash = 1511480045, _unused = 0 '\000', align = 8 '\b', fieldalign = 8 '\b', kind = 54 '6',
alg = 0x5827d0
gcdata = 0x52a3ac "\001\002\003\004\005\006\a\b\t\n\r\016\017\020\022\025\026\031\032\033\037,568
(gdb) p *a._type.ptrto._string
$16 = 0x4fc2b8 "*string"
(gdb) p *a._type.alg
$17 = {hash = {void (void *, uintptr, uintptr *)} 0x582860
bool *)} 0x582860
(gdb) p *a._type.alg.hash
$18 = {void (void *, uintptr, uintptr *)} 0x582860
(gdb) p *a._type.alg.equal
$19 = {void (void *, void *, bool *)} 0x582868
Думаю, на сей ноте можно закончить повествование. Я вас немного просветил и показал направление куда копать, а копать или нет — пусть каждый решит сам ;)
P.S. Всем желающим разобраться с runtime Go рекомендую ковырять свои програмки под gdb и читать исходники gccgo и Go.