Go Struct Literal Initialization Proposal: Bridging the Asymmetry Gap in Embedded Fields

Embedded pointer fields pose a challenge for Proposal #9859, but the benefits outweigh the complexity.

image.png|300

Note: The core content was generated by an LLM, with human fact-checking and structural refinement.

Go’s unique approach to composition through struct embedding is a core strength of the language. However, for years, this feature has suffered from a widely recognized, “counter-intuitive” inconsistency regarding how embedded fields are initialized versus how they are accessed. Recently, a decade-old proposal, #9859, has been re-activated and moved into the active review phase, aiming to resolve this crucial friction point.

Core Problem: Asymmetry

The fundamental issue lies in the asymmetry of read/write behavior versus initialization.

Go allows developers to leverage Field Promotion to access fields of an embedded struct directly, treating them as if they belonged to the outer struct itself.

Consider the following definitions:

1
2
3
4
5
6
7
type Point struct {
X, Y int
}
type Circle struct {
Point // Embedded Point
Radius int
}

Access/Write is Direct (Intuitive):

1
2
3
var c Circle
c.X = 10 // Direct access is highly intuitive and works due to Field Promotion
c.Y = 20

Initialization is Nested (Counter-intuitive): The same direct access intuition fails when initializing the struct literal. Attempting to initialize the promoted fields directly results in a compilation error (e.g., unknown field 'ID' in struct literal).

1
2
3
4
5
6
7
8
// Current Go: Compilation Error!
// c := Circle{X: 10, Y: 20, Radius: 5}

// Current Go: Must use冗长 (verbose) nested composite literals
c := Circle{
Point: Point{X: 10, Y: 20},
Radius: 5,
}

This stark asymmetry is the core pain point that Proposal #9859 seeks to address. Go core team members, including Brad Fitzpatrick and Andrew Gerrand, have independently experienced this frustration, initially believing the direct initialization syntax should be valid.

Proposal #9859 Details

Proposal #9859 suggests permitting developers to directly reference embedded fields within a struct literal. This would align the initialization process with the existing, intuitive field access mechanism.

The core concept mirrors a historical simplification in Go 1.5, which allowed the omission of type declarations in map literals when the type could be inferred from the context. In both scenarios (map keys in Go 1.5 and embedded fields now), the goal is to allow the compiler to infer the necessary type, enabling the developer to omit redundant type declarations.

Expected Syntax if Proposal #9859 is Accepted:

1
2
// Proposal expects this syntax to become valid
c := Circle{X: 10, Y: 20, Radius: 5}

Controversial Edge Case: Embedded Pointer Fields

The primary reason for the proposal’s slow movement over the last decade is the complexity introduced when the embedded field is a pointer type.

Consider a modified Circle struct:

1
2
3
4
5
6
7
type Point struct {
X, Y int
}
type Circle struct {
*Point // Embedded pointer to Point
Radius int
}

If a user attempts the proposed direct initialization, Circle{X: 10, …}, the underlying *Point field is initially nil. In a standard assignment statement (c.X = 10), dereferencing a nil pointer would cause a runtime panic.

Go core team member Ian Lance Taylor outlined three potential ways the language could handle this situation during struct literal initialization, each involving a trade-off between convenience, consistency, and safety:

Option Behavior Primary Trade-off Rationale
1. Implicit Allocation Automatically allocate memory for the embedded pointer (new(Point)). Convenience (User-friendly). Cons: Violates Go’s philosophy of “explicit over implicit” by hiding memory allocation, making performance analysis difficult, and potentially breaking encapsulation if the pointer points to a private type.
2. Runtime Panic The initialization attempts to write to the nil field and panics at runtime. Consistency (Simple rule). Pros: Follows the principle that struct literals should have the same semantics as sequential assignment statements (e.g., t1.A = 5 panics, so T{A: 5} should also panic). Cons: Postpones error detection until runtime.
3. Compilation Error The compiler statically detects the nil pointer situation and prevents compilation. Safety (Prevents runtime failures). Cons: Reduces developer experience and might incentivize avoiding pointer embedding even when it is the correct design choice.

The view favoring Option 2 (Runtime Panic) prioritizes consistency, arguing that the semantic behavior should remain identical to how assignments currently work outside of literals.

Real-World Impact Status

This technical debate has a tangible impact on daily development. Many developers acknowledge that the current requirement for verbose, nested literals is “too ugly,” often leading them to consciously avoid using struct embedding in API design, thereby sacrificing code clarity and reuse.

The utility of the proposal is supported by concrete data: Alan Donovan scanned the golang.org/x/tools and golang.org/x/net code repositories and identified 45 and 83 instances, respectively, where code could be simplified if this proposal were adopted.

Due to the clear community demand and general support from the core team, Proposal #9859 has been formally escalated to the active review stage. While the language design team still faces a careful and challenging decision regarding the pointer edge case—balancing convenience, consistency, and security—the successful passage of this proposal would mark a significant step forward in improving the Go developer experience and solidifying its composition philosophy.

Workarounds for Current Go

Until Proposal #9859 is implemented, developers must rely on explicit initialization methods:

  1. Nested Composite Literals: Explicitly name the embedded struct type to initialize its fields.
1
2
3
4
5
6
7
8
9
type Base struct { ID string }
type Child struct { Base a int b int }

// Required workaround for current Go
child := Child{
Base: Base{ID: "foo"}, // Explicitly initializing the embedded type
a: 23,
b: 42,
}

If structs are deeply nested, the nesting of composite literals becomes highly verbose. For instance, initializing a circle that embeds shapebase which embeds point requires repeating the embedded struct name twice: shapebase: shapebase{point: point{3.0}}.

  1. Sequential Assignment: Initialize the outer struct and then set the embedded fields using direct access.

    1
    2
    child := Child{ a: 23, b: 42 }
    child.ID = "foo" // Uses Field Promotion access, but requires two steps
  2. Constructor/Factory Functions: Define a helper function (often called a constructor or factory function) to encapsulate the necessary nested initialization logic, thereby promoting encapsulation at the package level.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    // Recommended workaround using a Factory function
    func NewCircle(pos Point, rad float64) *Circle {
    return &Circle{
    Point: Point{
    X: pos.X,
    Y: pos.Y,
    },
    Radius: rad,
    }
    }

More

Recent Articles:

Random Article:


More Series Articles about You Should Know In Golang:

https://wesley-wei.medium.com/list/you-should-know-in-golang-e9491363cd9a

And I’m Wesley, delighted to share knowledge from the world of programming. 

Don’t forget to follow me for more informative content, or feel free to share this with others who may also find it beneficial. It would be a great help to me.

Give me some free applauds, highlights, or replies, and I’ll pay attention to those reactions, which will determine whether I continue to post this type of article.

See you in the next article. 👋

中文文章: https://programmerscareer.com/zh-cn/go-direct-embedded/
Author: Medium,LinkedIn,Twitter
Note: Originally written at https://programmerscareer.com/go-direct-embedded/ at 2025-10-01 20:04.
Copyright: BY-NC-ND 3.0

Go Slices: Mechanics, Memory, and Design Philosophy Go Memory Management Evolution:Arena,Regions,and runtime.free

Comments

Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×