Menu

Golang Embed Struct: Embedding und promotete Methoden

Embedding setzt einen Typ ohne Feldnamen in einen anderen, sodass seine Felder und Methoden in den äußeren Typ befördert werden. Wie die Promotion funktioniert, Interfaces einbetten, Namenskonflikte und warum Embedding keine Vererbung ist.

Diese Seite enthält ausführbare Editoren - bearbeiten, ausführen und Ausgabe sofort sehen.

Embedding in einem Beispiel

Ein Feld mit Typ, aber ohne Namen ist ein eingebettetes Feld. Die Felder und Methoden des eingebetteten Typs sind direkt über den äußeren Typ erreichbar.

Ausgabe:

Ana
Hi, I'm Ana
ana@example.com
{User:{Name:Ana Email:ana@example.com} Level:2}

Der Name des eingebetteten Felds ist sein Typname, User. So sprichst du es in Literalen an (User: User{...}) und wenn du den inneren Wert selbst brauchst (a.User). Ein promotetes Feld lässt sich in einem Literal nicht über seinen kurzen Namen setzen: Admin{Name: "Ana"} scheitert mit unknown field Name in struct literal of type Admin.

Promotete Methoden erfüllen Interfaces

Die Promotion fügt die Methoden des eingebetteten Typs dem Method Set des äußeren Typs hinzu. Also erfüllt der äußere Typ jedes Interface, das der innere erfüllt.

Die Regeln für Method Sets richten sich nach dem eingebetteten Typ. Bettest du T ein, werden die Methoden mit Value Receiver von T sowohl zum äußeren Wert als auch zum äußeren Pointer befördert, die Methoden mit Pointer Receiver von *T aber nur zum äußeren Pointer. Bettest du *T ein, werden beide Sets zu beiden befördert. Im Zweifel bette den Pointer ein oder nutze den äußeren Typ über einen Pointer.

Einen Pointer einzubetten hat seinen Preis: Der Pointer kann nil sein. Service{} ohne Logger kompiliert, und svc.Log(...) löst dann eine Panic aus.

Embedding ist keine Vererbung

Embedding sieht aus wie Subklassenbildung, aber drei Dinge verhalten sich anders als in Java oder Python.

Der äußere Typ ist nicht der innere Typ. Ein Admin ist kein User. Eine Funktion, die einen User nimmt, akzeptiert kein Admin; übergib a.User.

Es gibt kein virtuelles Dispatching. Eine promotete Methode läuft auf dem eingebetteten Wert und weiß nichts vom äußeren Typ. Ruft sie eine andere Methode auf, ist es die Version ihres eigenen Typs, selbst wenn der äußere Typ eine gleichnamige definiert.

Ausgabe:

woof
The animal says ...

In einer klassenbasierten Sprache würde Speak „woof“ ausgeben. In Go ist d.Speak() die Kurzform für d.Animal.Speak(), und der Receiver dieses Aufrufs ist das Animal. Willst du Verhalten, das sich je nach Typ unterscheidet, nimm ein Interface: Übergib einen Sounder an den Code, der ihn braucht.

Der innere Typ weiß nicht, dass er eingebettet ist. Es gibt kein super. Um eine promotete Methode zu erweitern, definier auf dem äußeren Typ eine gleichnamige Methode und ruf die innere explizit auf:

func (a Admin) Greet() string {
	return a.User.Greet() + " (admin)"
}

Namenskonflikte und Verschattung

Ein Feld oder eine Methode des äußeren Typs verschattet ein gleichnamiges promotetes Element, wie Dog.Sound oben. Der weniger tief liegende Name gewinnt.

Befördern zwei eingebettete Typen auf derselben Tiefe denselben Namen, wird dieser Name mehrdeutig. Das Programm kompiliert trotzdem, solange nichts den mehrdeutigen Namen benutzt:

Löse den Konflikt, indem du den vollen Pfad benutzt oder ID auf dem äußeren Typ definierst.

Interfaces einbetten

Interfaces können andere Interfaces einbetten. Die Standardbibliothek baut auf diese Weise größere Interfaces:

type ReadWriter interface {
	Reader
	Writer
}

Auch ein Struct kann ein Interface einbetten. Das Struct erfüllt dieses Interface dann über den Wert, den du im Feld speicherst, und du überschreibst nur die Methoden, die dich interessieren. Das ist das Decorator-Pattern mit fast keinem Code:

Das explizite c.Reader.Read(p) ist nötig. c.Read(p) innerhalb von Read würde sich endlos selbst aufrufen.

Ein Interface in einen Test-Fake einzubetten ist eine verbreitete Abkürzung: Bette das große Interface ein, implementier die eine Methode, die der Test braucht, und lass den Rest weg. Jede nicht implementierte Methode löst beim Aufruf eine Panic wegen Nil-Pointer-Dereferenzierung aus, und in einem Test willst du oft genau das.

Ein verbreiteter Praxisfall: sync.Mutex einbetten

type Stats struct {
	sync.Mutex
	hits map[string]int
}

func (s *Stats) Hit(page string) {
	s.Lock()
	defer s.Unlock()
	s.hits[page]++
}

Das liest sich gut, exportiert aber auch Lock und Unlock als Teil der API von Stats, also kann jeder Aufrufer dein Struct sperren. Bei Typen, die außerhalb des Pakets genutzt werden, hält ein benanntes Feld (mu sync.Mutex) das Lock privat. Diese Abwägung gilt für jedes Embedding: Alles, was der eingebettete Typ exportiert, wird Teil der öffentlichen Oberfläche deines Typs.

Häufige Fehler

  • Virtuelles Dispatching erwarten. Promotete Methoden rufen nie die Überschreibungen des äußeren Typs auf.
  • Promotete Felder in einem Literal setzen. Nimm den Namen des eingebetteten Typs: Admin{User: User{Name: "Ana"}}.
  • Eingebettete nil-Pointer. Ein eingebettetes *T oder Interface muss gesetzt sein, bevor seine Methoden aufgerufen werden.
  • Versehentliche API-Oberfläche. Embedding exportiert alle exportierten Methoden des inneren Typs auf deinem Typ.

Häufig gestellte Fragen

Was ist Struct Embedding in Go?

Ein Feld, das nur mit einem Typ und ohne Namen deklariert wird: type Admin struct { User; Level int }. Felder und Methoden des eingebetteten User werden befördert, also funktionieren a.Name und a.Greet() direkt auf einem Admin. Der eingebettete Wert ist trotzdem ein normales Feld, erreichbar als a.User.

Hat Go Vererbung?

Nein. Go hat keine Klassen und keine Vererbung von Subtypen. Embedding ermöglicht Wiederverwendung durch Komposition: Der äußere Typ bekommt die Methoden des inneren, aber ein Admin ist kein User. Du kannst kein Admin übergeben, wo ein User erwartet wird, und promotete Methoden können nicht zurück in die Methoden des äußeren Typs rufen. Polymorphie kommt in Go von Interfaces.

Wie initialisiert man ein eingebettetes Struct in Go?

Im Composite Literal benennst du das eingebettete Feld mit seinem Typnamen: Admin{User: User{Name: "Ana"}, Level: 2}. Promotete Felder kannst du im Literal nicht direkt setzen: Admin{Name: "Ana"} scheitert mit unknown field Name in struct literal of type Admin.

Kann man in Go ein Interface in ein Struct einbetten?

Ja. Das Struct erfüllt das Interface dann über den eingebetteten Wert, und du kannst einzelne Methoden überschreiben. Das ist bei Decorators und Test-Fakes verbreitet. Ist das eingebettete Interface-Feld nil, löst der Aufruf einer nicht überschriebenen Methode eine Panic wegen Nil-Pointer-Dereferenzierung aus.

Coddy programming languages illustration

Lerne mit Coddy zu programmieren

LOS GEHT'S