golang) unexported struct pointer embedding

Unexported Struct Pointer Embedding

An exported struct embeds an unexported struct pointer

Prevents arbitrary creation of structs from external packages, enforcing the use of designated constructors or validation logic.

e.g.

 1type completedConfig struct {
 2	Options options.CompletedOptions
 3	Aggregator    aggregatorapiserver.CompletedConfig
 4	KubeAPIs      controlplane.CompletedConfig
 5	ApiExtensions apiextensionsapiserver.CompletedConfig
 6
 7	ExtraConfig
 8}
 9
10type CompletedConfig struct {
11	// Embed a private pointer that cannot be instantiated outside of this package.
12	*completedConfig
13}
Goal

Enforced Constructor

  • Prevents arbitrary creation or modification of objects via struct literals from external packages
    • External packages cannot create (initialize) structs with unexported fields using literals
  • Enforces factory methods
    • Ensures that essential validation or defaulting is completed before an object is created

Typical Encapsulation

1type CompletedConfig struct {
2	// Fields are unexported, so they are inaccessible from external packages
3	options options.CompletedOptions
4	aggregator aggregatorapiserver.CompletedConfig
5}
  • Values cannot be read from outside
  • Requires Getter methods for all fields

Unexported Pointer Embedding

1type completedConfig struct { // unexported struct
2	Options options.CompletedOptions // Exported Field!
3	Aggregator aggregatorapiserver.CompletedConfig // Exported Field!
4}
5
6type CompletedConfig struct {
7	*completedConfig // Embedding
8}
  • From outside, it's possible to directly access fields like cfg.Options, as if they were its own fields.
  • However, if you try to create CompletedConfig itself ({}), literal initialization is blocked because the *completedConfig type is unexported.

Advantages
  • Creation Restriction (Safety): Prevents arbitrary creation using CompletedConfig{...} (struct literal) (enforces constructor functions).
  • Access Convenience: When using the object, instead of tediously calling methods like cfg.GetOptions(), you can directly access variables like cfg.Options (no need to write Getter methods).
Disadvantages
  • Difficult to use with libraries that internally rely on reflection.
  • Difficult to create Mocks or Stubs when writing unit test code.

Recommended for infrastructure objects or core library design where stability must be enhanced by fundamentally blocking incorrect usage.


Post
Category
Series