Go ist eine moderne, sichere und gleichzeitig performante Programmiersprache. Dennoch gibt es einige Eigenheiten, die auf den ersten Blick verwirrend wirken können – besonders wenn man aus einer Sprache wie C kommt. Ein solches Beispiel ist das Verhalten von nil
in Interfaces.
Ein einfaches Beispiel: Warum ist nil
nicht nil
?
Angenommen, wir haben folgenden Go-Code:
package main
import "fmt"
type MyInterface interface {}
type MyStruct struct {
Name string
}
func getNilPointer() *MyStruct {
return nil
}
func main() {
var iface MyInterface = getNilPointer()
if iface == nil {
fmt.Println("Interface ist nil")
} else {
fmt.Println("Interface ist nicht nil")
}
}
Was denkst du, gibt dieses Programm aus?
Erwartete Ausgabe: Interface ist nil
(weil getNilPointer()
ja nil
zurückgibt).
Tatsächliche Ausgabe: Interface ist nicht nil
Warum passiert das?
Obwohl Go oft als C ähnlich betrachtet wird, gibt es in Go fundamentale Unterschiede: In Go besteht ein Interface-Wert aus zwei Teilen:
- Der dynamische Typ – Welcher konkrete Typ hinter dem Interface steckt.
- Der dynamische Wert – Der tatsächliche Wert dieses Typs.
In unserem Beispiel geschieht Folgendes:
getNilPointer()
gibt(*MyStruct)(nil)
zurück.- Dieses
nil
wird iniface
gespeichert, das vom TypMyInterface
ist. - Da
iface
nun einen konkreten Typ (*MyStruct
) hat, ist das Interface selbst nichtnil
, auch wenn sein Wertnil
ist.
Ein Interface ist in Go nur dann wirklich nil
, wenn sowohl der dynamische Typ als auch der Wert nil
sind.
Die richtige nil
-Prüfung
Falls wir tatsächlich testen möchten, ob das Interface einen nil
-Wert enthält, müssen wir den zugrundeliegenden Wert prüfen:
if iface == nil {
fmt.Println("Interface ist nil")
} else if ptr, ok := iface.(*MyStruct); ok && ptr == nil {
fmt.Println("Interface enthält einen nil-Pointer")
} else {
fmt.Println("Interface ist nicht nil")
}
Jetzt wird korrekt erkannt, dass iface
zwar nicht nil
ist, aber dennoch einen nil
-Pointer enthält.
Warum passiert das nicht in C?
In C gibt es keine Interfaces, sondern nur Pointer. Ein Vergleich mit NULL
ist direkt möglich:
#include <stdio.h>
struct MyStruct {
char *name;
};
struct MyStruct* getNilPointer() {
return NULL;
}
int main() {
void *iface = getNilPointer();
if (iface == NULL) {
printf("Pointer ist NULL\n");
} else {
printf("Pointer ist nicht NULL\n");
}
}
Hier gibt das Programm erwartungsgemäß Pointer ist NULL
aus, weil es keine zusätzliche Typinformation gibt, die das Verhalten verändern könnte.
Wie könnte das Problem umgangen werden?
Es gibt mehrere Wege, dieses Problem zu vermeiden:
1. Typassertion nutzen (Cast auf konkreten Typ)
Falls wir trotzdem mit Pointern arbeiten möchten, können wir eine Typassertion verwenden, um zu prüfen, ob der Wert im Interface tatsächlich nil
ist:
if ptr, ok := iface.(*MyStruct); ok && ptr == nil {
fmt.Println("Interface enthält einen nil-Pointer")
}
Diese Methode funktioniert zuverlässig, da sie sicherstellt, dass der gespeicherte Wert innerhalb des Interface geprüft wird.
2. Rückgabewert als Interface deklarieren
Eine andere Möglichkeit ist, die Signatur der Funktion zu ändern, sodass sie direkt ein Interface zurückgibt. Damit bleibt das Interface nil
, wenn der Rückgabewert nil
ist:
func getNilInterface() MyInterface {
return nil
}
func main() {
var iface MyInterface = getNilInterface()
if iface == nil {
fmt.Println("Interface ist nil")
} else {
fmt.Println("Interface ist nicht nil")
}
}
Hier gibt das Programm korrekt Interface ist nil
aus, weil das Interface direkt mit nil
initialisiert wird und kein dynamischer Typ gesetzt wird.
Fazit
Go-Interfaces sind mächtig, aber ihr Verhalten kann unerwartet sein, wenn man aus C oder anderen Sprachen mit reinen Pointern kommt. Besonders die Unterscheidung zwischen einem nil
-Interface und einem Interface, das einen nil
-Pointer enthält, ist essenziell.
Ich merke mir für die Zukunft
- Ein Interface ist nur
nil
, wenn sowohl der dynamische Typ als auch der Wertnil
sind. - Ein Interface mit einem
nil
-Pointer ist nichtnil
, weil es einen konkreten Typ hat. - Falls du
nil
überprüfen willst, verwende eine Typassertion.
Natürlich gibt es noch eine weitere Möglichkeit: Statt auf nil
zu überprüfen, könnte man auch einen error
zurückgeben und diesen auswerten. Dann müsste man sich nicht mehr unbedingt auf korrekt gesetzte nil
Interfaces verlassen. Aber das ist dann wieder ein anderes Thema.
Für mehr Informationen:
Go Sprachdefinition,
Comparison operators https://go.dev/ref/spec#Comparison_operators
zuletzt aufgerufen am 11.03.2025