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 :)