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

    Всем привет!

    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 <runtime.algarray+224>, 
      gcdata = 0x52a3ac "\001\002\003\004\005\006\a\b\t\n\r\016\017\020\022\025\026\031\032\033\037,568<?AUr~\236\237\325\365\370\377", _string = 0x50e5e0, x = 0x4b8e40, ptrto = 0x4b1ba0}
    

    Здесь мы видим примерно всю ту же информацию о типа данных «скрывающемся» за пустым интерфейсом.

    (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 <runtime.algarray+80>, 
      gcdata = 0x52a3ac "\001\002\003\004\005\006\a\b\t\n\r\016\017\020\022\025\026\031\032\033\037,568<?AUr~\236\237\325\365\370\377", _string = 0x5045d0, x = 0x0, ptrto = 0x0}
    (gdb) p *a._type.ptrto._string
    $16 = 0x4fc2b8 "*string"
    (gdb) p *a._type.alg
    $17 = {hash = {void (void *, uintptr, uintptr *)} 0x582860 <runtime.algarray+224>, equal = {void (void *, void *, 
        bool *)} 0x582860 <runtime.algarray+224>}
    (gdb) p *a._type.alg.hash
    $18 = {void (void *, uintptr, uintptr *)} 0x582860 <runtime.algarray+224>
    (gdb) p *a._type.alg.equal
    $19 = {void (void *, void *, bool *)} 0x582868 <runtime.algarray+232>
    

    Думаю, на сей ноте можно закончить повествование. Я вас немного просветил и показал направление куда копать, а копать или нет — пусть каждый решит сам ;)

    P.S. Всем желающим разобраться с runtime Go рекомендую ковырять свои програмки под gdb и читать исходники gccgo и Go.
    Поделиться публикацией

    Комментарии 4

      0
      спасибо за статью!
      а о GC будет? :)
        0
        Когда-нибудь в будущем =)
          0
          оно уже настало ;)
            0
            Всё относительно, друг Горацио…

      Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

      Самое читаемое