Go Structs: Difference between revisions

From NovaOrdis Knowledge Base
Jump to navigation Jump to search
Line 66: Line 66:


<font color=darkkhaki>Refactor this: [[Go Type Embedding]] and merge it here.</font>
<font color=darkkhaki>Refactor this: [[Go Type Embedding]] and merge it here.</font>
An embedded type is initialized by declaring a name of a struct inside other <tt>struct</tt> - ''without associating it with a field''. An embedded type is sometimes referred to as ''anonymous field''. It models an ''is-a'' relationship. Example:
<pre>
type A struct {
    f1 int
}
type B struct {
    A
    f2 int
}
</pre>
=====Methods and Embedded Types=====
A pointer receiver method associated with the embedded type also works with the embedding type. For example, if there's a:
<pre>
func (a *A) m1(...) {
    ...
}
</pre>
<tt>m1()</tt> also works directly with B instances as such:
<pre>
bPtr := new(B)
...
bPtr.m1(...)
</pre>
In the code sequence above, the invocations <tt>bPtr.m1(...)</tt> and <tt>bPtr.A.m1(...)</tt> are equivalent.


=<tt>struct</tt> Zero Value=
=<tt>struct</tt> Zero Value=

Revision as of 02:11, 20 March 2024

External

Internal

Overview

Structs are user-defined composite types, grouping together instances of arbitrary types, into one object.

A struct declaration consists in a series of fields, where each fields has an arbitrary name and a type. A struct may also contain other types, named embedded types and also methods that operate on the structure's fields. The fields usually represent a has-a relationship, consistent with the struct's composite type nature.

A struct variable is a value, not a reference variable, which means that no two different struct variables may point to the same struct instance.

Structs can represent data values that could have either a primitive or non-primitive value. "Primitive" structs imply creating a new instance every time an existing value is mutated. In this case, values themselves, rather than pointers, are used to share values of those structs. An example is the Time structure in the time package. However, in most cases, structs exhibit a non-primitive structure: adding or removing something from the value mutates the value, does not create a new value.

Printing Structs

To print the abbreviated struct, and only display values, and not field names, use:

fmt.Println(s)

To print both field names and values, use the %+v format specifier:

fmt.Printf("%+v\n", s)

Declaration

Struct types can be declared at package level or inside a function.

The struct type definition is introduced by the type keyword, to indicate that this is a user-defined type, followed by the new type name and the keyword struct, followed by a curly-brackets-enclosed enumeration of fields.

Fields

Each field is defined by an arbitrary name and a type. The type can be a pre-declared type or a user-defined type, such as another struct or an interface. No comma are required after fields.

type Item struct {
    color string
    size int
}

Fields with the same types can be collapsed:

type SomeStruct struct {
  ...
   i, j, k int
  ...
}

Once the type has been defined in a package or a function, variables of that type can be declared using the long declaration:

var i Item

If no explicit initialization follows, the struct variables declared as above have all their fields initialized with zero values.

Short declaration can also be used, as shown in the Initialization section, below.

Fields can be exported outside the package declaring the type. For more details see Exporting Fields below.


TODO

Embedded Types

Refactor this: Go Type Embedding and merge it here.

An embedded type is initialized by declaring a name of a struct inside other struct - without associating it with a field. An embedded type is sometimes referred to as anonymous field. It models an is-a relationship. Example:

type A struct {
    f1 int
}

type B struct {
    A
    f2 int
}
Methods and Embedded Types

A pointer receiver method associated with the embedded type also works with the embedding type. For example, if there's a:

func (a *A) m1(...) {
    ...
}

m1() also works directly with B instances as such:

bPtr := new(B)
...
bPtr.m1(...)

In the code sequence above, the invocations bPtr.m1(...) and bPtr.A.m1(...) are equivalent.

struct Zero Value

Expand this, struct zero value is important when parsing YAML and entire subtrees are missing.

Empty struct

An empty struct allocates zero bytes when values of this type are created. They are useful when a type, but not state, is needed. Example:

// we declared an empty struct that defines the type "A"
type A struct{}

a := A{}
b := struct{}{}

Idiomatic Struct Naming Conventions

Since references include package names, variable names should not be prefixed with the package name. If the package name is xml, use the name Reader, not XMLReader. In the second case, the full name would be xml.XMLReader.

Also see:

Go Style

Literal

Struct literals can have all field initialization values on the same line, or on different lines:

i := Item{color: "blue", size: 5}
i := Item {
  color: "blue",
  size: 5, // mandatory comma
}

There is "short" literal where the name of the fields are omitted, provided that the order in which they are declared is maintained:

i := Item {"blue", 5}

Embedded Literals

When a field of a struct is another struct, embedded literals can be used in initialization:

i := Item {
  color: "blue",
  packaging: {
    material: "plastic",
    protectionLevel: 10,
  }
  size: 5,
}

Initialization

Initialize a struct variable with an empty struct using the new() built-in function and the short variable declaration:

i := new(Item)

This results in an "empty" structure, with all fields initialized to zero. Note new() returns a pointer to the structure and not the structure itself.

A struct literal can also be used:

i := Item{color: "blue", size: 5}

Initializing Struct Fields to Something Else than Zero Value

It seems not to be possible. If I have a Set structure that internally contains a map, I have two options:

1. Use a NewSet() constructor that initializes the map.

2. Do lazy initialization on operations.

Operators

The Dot Operator

Individual fields in a struct can be read and modified using the "dot notation", the . operator.

var i Item
i.color = "blue"
i.size = 2

fmt.Printf("color %s, size %d\b", i.color, i.size)

Note that the . operator works with a regular struct variable as well as with a pointer to the struct. The compiler knows how to compensate for both of these cases:

i := new(Item)
fmt.Printf("i: %p\n", i)    // i is a pointer
i.color = "blue"            // the dot operator is applied to a pointer
i2 := Item{"red", 1}
fmt.Printf("i2: %+v\n", i2) // i2 is a struct
i2.color = "yellow"         // the dot operator is applied to a struct

Exporting Structs

"Exporting Structs" section needs refactoring.

Even if the enclosing struct type is exported by a package, not all fields are exported automatically, only those whose first character is an upper case letter. This behavior makes possible to have "private" fields in a public structure, when the structure is used outside its package.

Encapsulation and Private Fields

Formally define the semantics of fields that start with lower case names. They are "hidden". What exactly does that mean? Apparently, other package cannot see them.

Aslo see:

Object Oriented Programming in Go | Encapsulation

Exporting Fields

The fields of an exported struct type can be exported or unexported on a field-by-field basis, by naming them with an uppercase and respectively lowercase letter.

What if the struct is named with a lowercase letter and the field starts with an uppercase letter?

Explain this behavior: YAML_in_Go#TODO_Ee4.

Exporting Embedded Types

If the name of an embedded type starts with a lower case, it is unexported even if its outer type is exported. However, the fields Even if the name of the inner type is not exported, its fields may be individually exported, and thus accessible from outside the package.

Structs as Receiver Types

Object Oriented Programming in Go | Structs as Receiver Types

Tags

Struct tags are syntactical constructs introduced by `...` (backticks), and declared for struct fields. They provide a specification of how the struct fields should be serialized into JSON Is that true? Only JSON or it is more generic than that? and how the serialized information should be deserialized into the struct fields.

type User struct {
  Name string `example:"name"`
}
type User struct {
  Name string `json:"name"`
}

To PROCESS: https://www.digitalocean.com/community/tutorials/how-to-use-struct-tags-in-go

TO DISTRIBUTE

Field Tags and Attributes

A field declaration may be followed by an optional string literal tag, which becomes an attribute for all the fields in the corresponding field declaration. The tags are made visible through a reflection interface and take part in type identity for structs but are otherwise ignored.

Example:

type A struct {
    name string `json:"name"`
}

In the example above, tags have been included to provide metadata the JSON decoding function needs to parse JSON content. Each tag maps a field name in the struct type to a filed name in the JSON document.