Трохи про порожніх інтерфейсах. Швидкий погляд зсередини

Всім привіт!

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.
Джерело: Хабрахабр

0 коментарів

Тільки зареєстровані та авторизовані користувачі можуть залишати коментарі.