Структурное встраивание интерфейсов, паника: ошибка времени выполнения - PullRequest
0 голосов
/ 28 января 2019

Я пробую пример, связанный со встраиванием в структуру интерфейсов

// https://talks.golang.org/2014/go4java.slide#52
// Struct embedding of interfaces
// https://play.golang.org/p/SYiZ7M1OEhU

package main

import (
    "bytes"
    "fmt"
    "net"
)

// net.Conn has Read and Write

type loopBack struct {
    net.Conn
    buf bytes.Buffer
}

func (c *loopBack) Read(b []byte) (int, error) {
    fmt.Println("loopBack Read")
    return 0, nil 
}

func main() {

    loop := loopBack{}
    loop.Read(nil)
    loop.Write(nil)                                                                           
}

, а метод Write не определен, поэтому я получаю эту ошибку времени выполнения

panic: runtime error: invalid memory address or nil pointer dereference
[signal SIGSEGV: segmentation violation code=0xffffffff addr=0x0 pc=0xe28ca]

goroutine 1 [running]:
main.main()
    /tmp/sandbox812386031/main.go:28 +0x6a

Существуют некоторыеспособ проверить это во время компиляции?

ссылка на код

https://play.golang.org/p/SYiZ7M1OEhU

Ответы [ 2 ]

0 голосов
/ 28 января 2019

Golang не требует явных реализаций интерфейса.Что вы делаете здесь:

type loopBack struct {
    net.Conn
    buf bytes.Buffer
}

Это похоже на:

type loopBack struct{
    Conn net.Conn
    buf bytes.Buffer
}

net.Conn, являющийся типом интерфейса, первое поле вашего типа loopBack может быть любым, что реализуетинтерфейс net.Conn, который, конечно, больше, чем Read и Write ( см. здесь ).

Преимущество типов вложения заключается в том, что поля и функции-получатели (кроме конфликтов имен) можно напрямую обращаться к типам, которые их встраивают.

Со встроенным полем net.Conn вы действительно можете написать:

loop.Write(nil)

Если поле Conn инициализируется (в противном случае его значение равно nil).Если изменить объявление на вторую версию, loop.Write не сработает, вам придется написать:

loop.Conn.Write(nil)

Внедрение шрифтов очень мощное, но при первом запуске у вас есть ряд ошибок.К счастью, есть целый параграф, объясняющий встраивание в эффективный документ go

В любом случае, как уже говорилось, вы можете вызывать функции и обращаться к полям (если вы вместо этого встраиваете тип структурыинтерфейса).Однако есть одна вещь: поле должно быть правильно инициализировано!

И именно здесь вы ошиблись: вам все равно придется инициализировать вашу Conn часть переменной loopBack,в противном случае вы делаете то же самое, что и:

net.Conn(nil).Write(nil)

Что, естественно, приводит к панике (разыменование нулевого указателя) ...

Например:

conn, err := net.Dial("tcp", "localhost:80")
if err != nil {
    log.Fatalf("failed to dial localhost: %+v", err)
}
loop := loopBack{
    Conn: conn,
}
loop.Write(nil) // same as conn.Write(nil)

Неспособность установить встроенное поле net.Conn сродни выполнению чего-то вроде этого:

s := make([]*int, 10) // make slice of 10 pointers to int
fmt.Println(len(s)) // outputs 10
*s[0]++ // add 1 to first element PANICS

10 элементов в срезе существуют, но все они были инициализированы в nil

0 голосов
/ 28 января 2019

То, что вы делаете, это , а не , то же самое, что сказать "loopBack implements net.Conn".
Чтобы получить ошибки во время компиляции об отсутствующем методе - и несоответствие Read() тоже- объявить тип loop:
(И не вставлять net.Conn в loopBack)

func main() {
  var loop net.Conn = loopBack{}

...