Article in English can be found on dev.to/arxeiss/in-go-nil-is-not-equal-to-nil-sometimes-jn8
Následující kód je pěkným příkladem, kdy proměnná val je nil ale porovnání ve funkci je vyhodnoceno jako false. Podobně pak i v případě readFile, který vrací error. Kód je možné spustit v Go Playgroundu a ověřit si tak, že opravdu nekecám.
func nilPrint(val interface{}) string {
if val == nil {
return "I got nil"
}
return fmt.Sprintf("I got %T type with value '%v'", val, val)
}
func readFile() error {
var err *fs.PathError
fmt.Println(err == nil)
return err
}
func main() {
// Prints: I got string type with value 'Some value'
fmt.Println(nilPrint("Some value"))
// Prints: I got nil
fmt.Println(nilPrint(nil))
// Prints: true
// but then: I got *string type with value '<nil>'
var val *string
fmt.Println(val == nil) // Prints true
fmt.Println(nilPrint(val))
fmt.Println("\nWith error interface")
// Prints: true (inside readFile)
// but then: false (in main method)
err := readFile()
fmt.Println(err == nil)
}
Důvodem je interface
Program se tak chová proto, že funkce nilPrint očekává na vstupu interface{}, což značí prázdný interface, a tedy něco jako Any. Druhé funkce vrací error, což je také interface. Porovnání s nil pak vrací false, protože tato proměnná je sice nulová, ale má také datový typ. Pokud se porovnává proměnná známého typu s nil, tento problém nenastává.
Řešení pomocí reflexe
Oprava funkce, která vrací chybu je jednoduchá. Nedávat proměnné err konkrétní typ, ale ponechat typ error. Tedy změnit první řádek takto var err error. Ostatně proměnné, které obsahují chyby, by nikdy neměly mít konkrétní datový typ.
V druhém případě je to ale složitější. Z definice funkce je zřejmé, že je potřeba přijímat cokoli. Řešení tohoto problému už je pouze v použití reflexe.
if val == nil || (reflect.ValueOf(val).Kind() == reflect.Ptr && reflect.ValueOf(val).IsNil()) {
return "I got nil"
}
Volání metody na nulovém objektu
Nevím, jestli to přímo souvisí s výše popsaným problémem. Ale v jiných jazycích toto možné není, tak jsem se rozhodl to zde zmínit.
Go nemá klasické metody jako jiný OOP jazyk. V Go se to nazývá receivers, což se dá přeložit jako příjemce. A zde nastává také první velký rozdíl. Pokud je proměnná nějakého datového typu, ale zároveň její hodnota je nil, lze stále volat tyto receivery či metody. Kód níže tak neskončí s panic chybou, ale úplně v pohodě proběhne. Lze opět vyzkoušet na Go Playground.
type JSONParser struct {
raw string
}
func (p *JSONParser) Length() int {
if p == nil {
return 0
}
return len(p.raw)
}
func main() {
var parser *JSONParser
fmt.Println(parser.Length()) // Prints: 0
parser = &JSONParser{raw: `{"just": "show", "some_result": 255}`}
fmt.Println(parser.Length()) // Prints: 36
}
Zkušenosti a postřehy s Go můžete sdílet se čtenáři v komentářích
K tomuto článku již není možné přidávat další komentáře
Komentáře
Tuto vlastnost Go radši nebudu komentovat :) Ale volat metody nad NULL adresou v C++ jde (samozřejmě je to UB :) ).
class X { void print() { cout << "x"; }};
int main() {
X* x = nullptr;
x->print();
}
Zajímavé, že to funguje i v C++ jsem netušil. Díky
Metody jsou běžné funkce, které akorát dostanou this jako skrytý parametr, takže pokud se v nich na this nešahá, tak teoreticky může být this cokoliv.
Ale samozřejmě "funguje" je silně v uvozovkách, protože to je UB, takže to může udělat cokoliv :)