Проверка на нулевые значения в вложенных элементах Голанга
У меня есть глубоко вложенная структура в го. Они построены Джоном Унмаршаллером.
Однако некоторые поля в этой структуре являются "пустыми", поэтому я заканчиваю работу со структурой, которая может содержать нули в разных местах.
Пример (реальная вещь еще глубже вложенная и большая: 400 строк структур):
package main
import "fmt"
type Foo struct {
Foo string
Bar *Bar
}
type Bar struct {
Bar string
Baz *Baz
}
type Baz struct {
Baz string
}
func main() {
f1 := Foo{Foo: "f1"}
f2 := Foo{Foo: "f2", Bar: &Bar{Bar: "br2"}}
f3 := Foo{Foo: "f3", Bar: &Bar{Bar: "br3", Baz: &Baz{Baz: "bz3"}}}
fmt.Println(f3.Bar.Baz.Baz) //-> bz3
fmt.Println(f2.Bar.Baz.Baz) //-> panic: runtime error: invalid memory address or nil pointer dereference
fmt.Println(f1.Bar.Baz.Baz) //-> panic: runtime error: invalid memory address or nil pointer dereference
//so far so good, but
//is there a more generic way to do this kind of testing?
if f2.Bar != nil && f2.Bar.Baz != nil {
fmt.Println(f2.Bar.Baz.Baz)
} else {
fmt.Println("something nil")
}
}
Вопрос в том, есть ли более общий способ проверить, является ли какой-либо узел в дереве ссылок нулевым? Мне нужно получить много разных предметов, и будет сложно написать все эти операторы if. Ох и скорость вызывает беспокойство;)
3 ответа
Если вы хотите избежать reflect
' (отражение, как "общий" способ проверки полей любого struct
, немного как в " Golang - Получить указатель на значение, используя отражение " или в этой сути: это медленнее), самый надежный способ - реализовать методы на Foo
чтобы вернуть правильное значение
func (foo *Foo) BarBaz() string {
if f2.Bar != nil && f2.Bar.Baz != nil {
return f2.Bar.Baz.Baz
} else {
fmt.Println("something nil")
return "" // for example
}
}
Если таких функций много, можно написать 1.4. go generate
Команда может помочь генерировать большинство из них.
Один из элегантных способов (на мой взгляд) справиться с этим - добавить геттеры к структурам, которые используются в качестве указателей. Этот "метод" также используется сгенерированным кодом Go для protobuf, и он позволяет естественным образом объединять вызовы методов, не беспокоясь о панике во время выполнения из-заnil
указатели.
В вашем примере Bar
а также Baz
структуры используются как указатели, поэтому вооружите их геттерами. Основное внимание уделяется добавлению методов с получателем указателя, и сначала необходимо проверить, является ли получательnil
. Если да, верните нулевое значение типа результата. Если нет, верните поле структуры:
func (b *Bar) GetBaz() *Baz {
if b == nil {
return nil
}
return b.Baz
}
func (b *Baz) GetBaz() string {
if b == nil {
return ""
}
return b.Baz
}
Преимущество методов с приемниками указателей заключается в том, что вы можете вызывать их с помощью nil
приемники. Это не вызывает паники во время выполнения, пока вы не попытаетесь сослаться на их поля, чего мы не делаем, поэтому сначала проверяем, является ли получательnil
(в конечном итоге приемники действуют как обычные параметры - и передача nil
как аргумент указателя).
Имея вышеуказанные геттеры, использование упрощается до этого, и паника во время выполнения не возникает ни в одном из этих примеров:
fmt.Println(f3.Bar.GetBaz().GetBaz()) // naturally no panic
fmt.Println(f2.Bar.GetBaz().GetBaz()) // No panic
fmt.Println(f1.Bar.GetBaz().GetBaz()) // No panic
if baz := f2.Bar.GetBaz(); baz != nil {
fmt.Println(baz.GetBaz())
} else {
fmt.Println("something nil")
}
Попробуйте на игровой площадке Go.
я использовалrecover
:
package navigate
func recoverFrom(err any, panicPred func(e error) bool) {
if err != nil {
e, ok := err.(error)
if !ok || panicPred(e) {
panic(err)
}
}
}
func RecoverFromNilPointerDereference() {
recoverFrom(
recover(),
func(e error) bool {
// unfortunately, there's no memory access object that can be matched, so we're forced to match by string
// if this internal string changes, this function will need to be changed to match the new value
const internalMemoryAccessErrorMsg = "runtime error: invalid memory address or nil pointer dereference"
return e.Error() != internalMemoryAccessErrorMsg
},
)
}
func Safely[T any](navigate func() T) T {
defer RecoverFromNilPointerDereference()
return navigate()
}
Использование:
navigate.Safely(func() *string {
return a.b.c
})