Приведение интерфейсов для базовых типов в Go

На работе завязалась интересная дискуссия: если в Go вообще всё можно привести к интерфейсу, является ли интерфейс некой базой для любого типа в Go? Как отслеживаются ошибки приведения типов для таких случаев? К примеру возьмем следующий код:

import (
    "fmt"
    "reflect"
)

func interfaceArg(i interface{})  {
    fmt.Println("type:", reflect.TypeOf(i))
}

func main() {
    var x float64 = 3.14
    interfaceArg(x)
}


На выходе получим как и ожидалось:

> type: float64

Если заглянуть в ассемблер, то становится очевидным что приведение к типу “интерфейс” проходит на лету перед вызовом функции interfaceArg:

        leaq    type.float64(SB), AX

Хорошо, но что с обработкой ошибок преобразования? В документации Go четко сказано, что проверка на корректность приведения интерфейса к типу проводится только в время исполнения. Значит, следующее небольшое изменение в функции interfaceArg будет успешно скомпилировано:

func interfaceArg(i interface{})  {
    val := i.(int)
    fmt.Println(val)
}

Но упадет на во время исполнения, так как в функцию была передана информация о том, что тип у нас все же float64:

panic: interface conversion: interface {} is float64, not int

Ассемблерное преобразования в interfaceArg выглядит следующим образом:

       pcdata  $2, $0
       pcdata  $0, $0
       movq    "".i+96(SP), AX
       pcdata  $2, $1
       leaq    type.int(SB), CX
       cmpq    AX, CX
       jne     interfaceArg_pc164
       pcdata  $2, $2
       pcdata  $0, $1
       movq    "".i+104(SP), AX
       pcdata  $2, $0
       movq    (AX), AX

Где interfaceArg_pc164 как раз и является той функцией, которая вызовет панику если тип не type.int. Все было вычислено во время выполнения, так как компилятор не знает с какими аргументами будет вызвана функция interfaceArg. А что если компилятор гарантированно знает тип во время компиляции?

func main() {
    var x float64 = 3.14
    var y interface{} = x
    z := y.(int)
    fmt.Println(z)
}

К ошибке на этапе компиляции этот код, ожидаемо не приведет и мы получим панику во время выполнения. Но что же там в ассемблере?!

        pcdata  $2, $1
        pcdata  $0, $0
        leaq    type.float64(SB), AX
        pcdata  $2, $0
        movq    AX, (SP)
        pcdata  $2, $1
        leaq    type.int(SB), AX
        pcdata  $2, $0
        movq    AX, 8(SP)
        pcdata  $2, $1
        leaq    type.interface {}(SB), AX
        pcdata  $2, $0
        movq    AX, 16(SP)
        call    runtime.panicdottypeE(SB)
        undef

А в ассемблере мы получаем безусловный вызов паники runtime.panicdottypeE, так как проверять ничего не нужно (типы заведомо не совпадают), но поведение хочется иметь стандартное для всех случаев, даже если можно обеспечить определение несоответствия типов во время компиляции.

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

Leave a Reply