Zum Hauptinhalt springen
Echtzeit-Radar & Feeds
Alle RSS Feeds ➔
👥 Community & Social
Windows Tipps & SecurityGrafikkarte vor Überhitzung schützen: So geht’s(25.09.2026 um 08:00 Uhr)
••••••••••
Windows Tipps & SecurityGrafikkarte vor Überhitzung schützen: So geht’s(25.09.2026 um 08:00 Uhr)
••••••••••
Intelligence View
⚡ tsecurity.de Intelligence

Struct Embedding in Go: Composition That Bites When You Reach for Inheritance

Book: The Complete Guide to Go Programming Also by me: Thinking in Go (2-book series) — Complete Guide to Go Programming + Hexagonal Architecture in Go My project: Hermes IDE | GitHub — an IDE for developers who ship with Claude Code an…

0
↗ Quelle (dev.to)
Reagiere als Erste:r — dein Feedback zählt!





You come to Go from a language with classes. You see struct

embedding for the first time, and it reads like inheritance. A field

with no name, methods that "carry over" to the outer type, a base

struct that your type extends. So you write code the way you always

have, and most of it works. Then a method does something you did not

ask for, a type satisfies an interface you never meant to implement,

or two embedded types fight over a name and the compiler shrugs until

the exact line that calls it.



Embedding is not inheritance. It is composition with a syntax that

promotes methods and fields up one level. Once you hold that

distinction, the surprises stop being surprises. Here is where they

come from.





Embedding promotes, it does not subclass



Write an embedded field by giving a type with no field name:




type Engine struct {
Horsepower int
}

func (e Engine) Start() string {
return "vroom"
}

type Car struct {
Engine // embedded
Brand string
}






Car now has a Start method and a Horsepower field, both

promoted from Engine. You can write car.Start() and

car.Horsepower as if they were declared on Car.




car := Car{Engine: Engine{Horsepower: 300}, Brand: "Fiat"}
fmt.Println(car.Start()) // vroom
fmt.Println(car.Horsepower) // 300






This is where the inheritance illusion starts. car.Start() is

sugar. The compiler rewrites it to car.Engine.Start(). The

receiver of Start is still an Engine, never a Car. There is no

base class, no super, no virtual dispatch. Engine does not know

Car exists.



That last point is the one that bites. A promoted method runs against

the embedded value, not the outer struct.





The method that ignores the outer struct



Say you want a stringer on the embedded type that reads outer fields.

This is the move that feels like overriding a base method.




type Base struct {
Name string
}

func (b Base) Describe() string {
return "base: " + b.Name
}

type User struct {
Base
Role string
}






You construct a User, set the role, and call Describe, expecting

the role to show up somehow.




u := User{Base: Base{Name: "ana"}, Role: "admin"}
fmt.Println(u.Describe()) // base: ana






There is no path from Describe to Role. Describe has a Base

receiver. Base has no idea what Role is. In a class hierarchy a

method on the parent can be overridden by the child and dynamic

dispatch picks the override. Embedding has no dispatch. If you want

User to describe itself, you declare the method on User:




func (u User) Describe() string {
return "user: " + u.Name + " (" + u.Role + ")"
}






Now User.Describe shadows the promoted Base.Describe. Calling

u.Describe() runs the User version. Calling u.Base.Describe()

still runs the base one. Both exist. You chose which by the selector,

not by runtime type.





Accidental interface satisfaction



This is the surprise that ships to production. Embedding a type drags

its whole method set into yours, and that method set can satisfy

interfaces you never intended to implement.




type Closer interface {
Close() error
}

type Conn struct{}

func (c *Conn) Close() error {
fmt.Println("closing real connection")
return nil
}

type Service struct {
*Conn // embedded for the Query method, say
}






You embedded *Conn because you wanted its query helpers. But

*Conn has Close, so *Service now satisfies Closer too. A

pool that ranges over Closer values and calls Close will close

your service's underlying connection, possibly one shared across the

program.




func closeAll(cs []Closer) {
for _, c := range cs {
c.Close()
}
}

// *Service slides into []Closer with no warning,
// and closeAll shuts its connection.






Nothing in the type declaration says "I am a Closer." The compiler

infers it from the promoted method set. If you did not mean to expose

Close, embedding was the wrong tool. Use a named field instead:




type Service struct {
conn *Conn
}

func (s *Service) Query(q string) error {
return s.conn.query(q)
}






Now *Service exposes only what you wrote. Close stays private to

the connection. The rule: embedding is a public promise. Everything

the embedded type exports, your type exports too.





Pointer vs value embedding changes the method set



Whether you embed Conn or *Conn decides which methods get

promoted, because of how Go builds method sets.



A method with a pointer receiver belongs to the method set of the

pointer type, not the value type. So if Conn has func (c *Conn)

Close()
, then:




  • Embedding Conn (value) into Service promotes Close only when
    you hold a *Service, because Close needs an addressable
    receiver.

  • Embedding *Conn (pointer) into Service promotes Close onto
    both Service and *Service.



type Service struct {
Conn // value embed
}

var s Service
var _ Closer = &s // works: &s is addressable, Close promoted
var _ Closer = s // compile error: Service does not implement Closer





The fix when you want the value type itself to satisfy the interface

is to embed the pointer, or to keep all receivers as values. Mixing

value and pointer receivers on the same type is where this gets hard

to reason about. Pick one receiver style per type and the method-set

rules stop fighting you.





Name collisions compile until you call them



Embed two types that both export the same method or field name, and

the outer struct compiles. The conflict is only an error at the

selector that uses the ambiguous name.




type Reader struct{}

func (Reader) Read() string { return "read" }

type Writer struct{}

func (Writer) Read() string { return "also read" }

type Pipe struct {
Reader
Writer
}






Pipe declares fine. You can construct it, store it, pass it around.

The Go spec says a name promoted from two embedded types at the same

depth is not promoted at all. So Pipe has no Read method.




var p Pipe
p.Read() // compile error: ambiguous selector p.Read






The collision sleeps until a caller writes p.Read(). If no caller

ever does, the ambiguity stays invisible. Then someone adds a call,

or assigns Pipe to an interface that needs Read, and the build

breaks far from where the embedding lives.



Depth matters too. A name at a shallower embed level wins over a

deeper one. So this resolves with no error:




type WriterHolder struct {
Writer // Read sits at depth 2 from Pipe
}

type Pipe struct {
Reader // depth 1
WriterHolder // depth 2 path to Writer.Read
}






The shallow Reader.Read is promoted; the deeper one is shadowed.

The asymmetry between "same depth is an error" and "different depth

silently picks the shallow one" is exactly the kind of rule that

reads fine and behaves surprisingly.



Resolve a real collision by promoting explicitly:




func (p Pipe) Read() string {
return p.Reader.Read()
}









When embedding is the right call



None of this means avoid embedding. It means use it for what it is:

composition where you genuinely want the embedded type's surface to

become part of yours.



Embedding fits when:




  • You want to extend a type's behavior and expose its methods on
    purpose, like wrapping http.ResponseWriter to add logging while
    forwarding everything else.

  • You are building a mixin of cross-cutting methods, like an embedded
    sync.Mutex so s.Lock() reads cleanly. Watch the value-copy trap
    there: embed it and use pointer receivers.

  • You implement an interface by embedding it and overriding one
    method, the standard decorator shape.




type loggingWriter struct {
http.ResponseWriter // promote the whole interface
status int
}

func (w *loggingWriter) WriteHeader(code int) {
w.status = code
w.ResponseWriter.WriteHeader(code) // forward
}






That is embedding doing its job. You wanted the full

ResponseWriter surface, and you overrode one method by shadowing.



Reach for a named field instead when you want the inner type's data

without its public method set, when you do not want to promise the

inner type's interfaces, or when "has-a" describes the relationship

better than "is-a-surface-of." Most domain types fall here. A

User has an Address; it does not want every Address method

showing up on User.






The one rule



Before you embed, ask one question: do I want every exported method

and field of this type to become part of my type's public surface,

and to satisfy whatever interfaces that surface satisfies? If yes,

embed. If you only want the data, or only some behavior, give it a

field name and forward what you need by hand. The extra lines are

cheaper than an accidental Closer.






Embedding is one of those Go features that looks like a port of a

class concept and turns out to be its own thing with its own rules.

The Complete Guide to Go Programming walks through method sets,

promotion, and interface satisfaction the way the spec defines them,

so the surprises in this post become the behavior you expect.

Hexagonal Architecture in Go shows where composition over

inheritance pays off when you are wiring ports and adapters.



Thinking in Go — the 2-book series on Go programming and hexagonal architecture

1. Sofort-Triage & Abwehrmaßnahmen

SOC Incident Playbook: Vulnerability Remediation & Verification
Syntax validiert (0 Fehler)
title: Detect Exploitation - Struct Embedding in Go: Composition That Bites When You Reach for Inheritance
id: e3a733e0-292e-4840-89d5-9f2b49109c26
status: experimental
description: Automatisch generierte SIEM-Erkennungsregel basierend auf CTI Intelligence
references:
  - https://tsecurity.de/
author: iShareStuff CTI Automated Detection Engine
date: 2026-09-25
logsource:
  category: network_connection
  product: any
detection:
  selection:
      CommandLine|contains:
        - 'exploit'
  condition: selection
falsepositives:
  - Legitime administrative Zugriffe oder Penetrationstests
level: high
tags:
  - attack.initial_access
Syntax validiert (0 Fehler)
rule CTI_Threat_Indicator {
    meta:
        author = "iShareStuff CTI Automated Detection Engine"
        date = "2026-09-25"
        description = "YARA Signature for "
    strings:
        $str = "Struct Embedding in Go: Compos" ascii wide
    condition:
        any of them
}
Syntax validiert (0 Fehler)
index=security sourcetype IN ("cisco:asa", "pan:traffic", "zeek_conn", "suricata", "WinEventLog:Security")
("Struct Embedding in Go Composition That ")
| stats count earliest(_time) as first_seen latest(_time) as last_seen by src_ip, dest_ip, dest_host, signature
| eval first_seen=strftime(first_seen, "%Y-%m-%d %H:%M:%S"), last_seen=strftime(last_seen, "%Y-%m-%d %H:%M:%S")
| sort - count
Syntax validiert (0 Fehler)
message: "*Struct Embedding in Go Composition That *"
Syntax validiert (0 Fehler)
CommonSecurityLog
| where Message has "Struct Embedding in Go Composition That "
| summarize EventCount = count(), FirstSeen = min(TimeGenerated), LastSeen = max(TimeGenerated) by SourceIP, DestinationIP, DestinationPort, Activity
| extend DetectionRule = "iShareStuff-CTI-Compiled"
| sort by EventCount desc

2. Cyber Threat Intelligence & Forensik

🎯
MITRE ATT&CK Matrix Navigator 14 Taktiken
Reconnaissance
-
Resource Development
-
Initial Access
Execution
Persistence
-
Privilege Escalation
Defense Evasion
Credential Access
-
Discovery
-
Lateral Movement
-
Collection
-
Command and Control
Exfiltration
-
Impact
tsecurity.de Cognitive Threat RAG
Fokus-Vektor:

Kognitive Analyse für identifizierte Bedrohung: Erhöhte Bedrohungslage im Bereich Struct Embedding in Go: Composition That.... Basierend auf 368k Vektor-Korrelationen werden sofortige Isolationsmaßnahmen für betroffene Endpunkte empfohlen.

🛡️ Angriffsfläche & Exposure

Netzwerk/Remote-Zugriff ohne Vorauthentifizierung möglich.

⚡ Empfohlene Sofortmaßnahmen
  • 1. Perimeter-Inspektion: Relevante Portfreigaben und exponierte Endpunkte unverzüglich scannen.
  • 2. Patch-Applikation: Hersteller-Hotfix einspielen oder betroffene Daemons in isolierte DMZ-Segmente überführen.
  • 3. Telemetrie & EDR-Alerts: Prozessaufrufe und Child-Processes auf anomale Shell-Spawns überwachen.
🔗 Semantisch verwandte Zero-Days MariaDB 11.7 VEC
Ähnliche Beiträge
🔍 Verwandte News

Auch interessante Nachrichten Struct Embedding in Go: Composition That Bites When You Reach for Inheritance

Thematisch verwandte Begriffe: Struct, Embedding, Composition, That · 6 Treffer

Laden...

Videos werden geladen ...

Laden...

Beiträge werden geladen ...

Laden...

Videos werden geladen ...

Laden...

Beiträge werden geladen ...

Laden...

Videos werden geladen ...

Laden...

Beiträge werden geladen ...

Laden...

Videos werden geladen ...

Laden...

Beiträge werden geladen ...

Laden...

Videos werden geladen ...

Zum Aktualisieren ziehen
ZERO-DAY CVE-2026-93647 | An unauthenticated calendar sender can place active markup in a COUNTER …
Advisory →
tsecurity.de Icon
Offline-Lesen, Eilmeldungen & 0ms Ladezeit

Installiere tsecurity.de direkt auf deinen Home-Bildschirm für das ultimative Vollbild-Magazinerlebnis ohne Browser-Leisten.

Nächster Beitrag