Немного о пустых интерфейсах. Быстрый взгляд изнутри

Всем привет!

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 , equal = {void (void *, void *,
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.

Комментарии (0)

© Habrahabr.ru