Understanding Composite Data Types in Go: Arrays, Slices, Maps, and Structs
Arrays
An array is a fixed-length sequence of zero or more elements of the same type.
Declaring an array:
var numbers [3]int // Elements are initialized to the zero value of the type (0 for int).
fmt.Println(numbers[0]) // Prints 0
Initializing an array:
var primes = [3]int{2, 3, 5} // Array literal initialization.
var values [3]int // Declare first.
values[0] = 10 // Initialize later.
values[1] = 20
sequence := [...]int{10, 20, 30} // Length determined by the number of initializers.
largeArray := [...]int{99: -1} // Defines a 100-element array with the last element as -1, others 0.
Using arrays:
fmt.Println(sequence[0]) // Access the first element.
// Iterate with index and value.
for index, val := range sequence {
fmt.Printf("Index: %d, Value: %d\n", index, val)
}
// Iterate for values only.
for _, val := range sequence {
fmt.Printf("Value: %d\n", val)
}
// Get array length.
fmt.Printf("Length: %d\n", len(sequence))
Slices
A slice is a variable-length sequence of elements of a single type, written as []T. It provides a view into an underlying array. A slice has three components: a pointer to the first accessible element, a length (number of elements), and a capacity (number of elements from the slice start to the end of the array). The zero value of a slice is nil.
Creating slices:
// Create an underlying array.
daysOfWeek := [...]string{"Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"}
// Create a slice from the array.
weekend := daysOfWeek[5:7] // Contains elements at indices 5 and 6.
// Syntax: [i:j] -> elements i through j-1.
// [:j] -> start to j-1.
// [i:] -> i to end.
// [:] -> all elements.
// Slice literals.
numbers := []int{1, 2, 3}
// Using make.
slice := make([]int, 3, 5) // Length 3, capacity 5.
Using slices:
// Appending elements.
numbers = append(numbers, 4) // Appends 4 to the slice.
// Copying slices.
var newSlice []int
copy(newSlice, numbers) // Copies elements from numbers to newSlice.
Maps
A map is an unordered collection of key-value pairs, written as map[KeyType]ValueType. Its zero value is nil.
Creating maps:
// Using make.
ageMap := make(map[string]int)
ageMap["Alice"] = 30
ageMap["Bob"] = 25
// Using a literal.
ageMap = map[string]int{
"Alice": 30,
"Bob": 25,
}
Iterating over maps:
for name, age := range ageMap {
fmt.Printf("%s is %d years old.\n", name, age)
}
Structs
A struct is an aggregate data type that groups zero or more named fields of possibly different types. Each field is called a member. If a member's name starts with an uppercase letter, its exported; otherwise, it is unexported. A struct cannot contain a field of its own type but can contain a pointer to its own type. The zero value of a struct consists of the zero values of its fields.
Defining a struct:
type Person struct {
ID int
Name string
Address string
}
// An empty struct.
var empty struct{}
// Empty struct as a map value (e.g., for a set).
set := make(map[string]struct{})
Creating and using struct variables:
var employee Person
// Assigning and accessing fields.
employee.Name = "Charlie"
fmt.Println(employee.Name)
// Using a pointer.
ptr := &employee.Name
fmt.Println(*ptr)
var empPtr *Person = &employee
empPtr.Address = "123 Main St" // Equivalent to (*empPtr).Address = ...
// Initializing with a struct literal.
p := Point{X: 1, Y: 2}
emp := Person{ID: 1, Name: "David"} // Uninitialized fields are set to zero values.
// Getting a pointer to a new struct.
pp := &Point{3, 4}
// Equivalent to:
// pp := new(Point)
// *pp = Point{3, 4}
Struct embedding (anonymous fields):
type Point struct{ X, Y int }
type Circle struct {
Point // Anonymous field (embedding).
Radius int
}
type Wheel struct {
Circle // Embedding Circle.
Spokes int
}
// Accessing promoted fields.
var w Wheel
w.X = 10 // Promoted from Point.
w.Y = 20 // Promoted from Point.
w.Radius = 5 // From Circle.
w.Spokes = 36
// Initialization.
w = Wheel{
Circle: Circle{
Point: Point{X: 10, Y: 20},
Radius: 5,
},
Spokes: 36,
}
Methods on structs:
A method is a function with a special receiver argument that binds it to a type.
// Method with a value receiver.
func (p Point) DistanceTo(q Point) float64 {
dx := q.X - p.X
dy := q.Y - p.Y
return math.Sqrt(dx*dx + dy*dy)
}
// Usage.
p1 := Point{1, 2}
p2 := Point{4, 6}
dist := p1.DistanceTo(p2)
// Method with a pointer receiver.
func (p *Point) Scale(factor float64) {
p.X = int(float64(p.X) * factor)
p.Y = int(float64(p.Y) * factor)
}
// Calling a pointer receiver method.
pt := &Point{5, 10}
pt.Scale(2.0)
// Also valid with a value; Go automatically takes the address.
pt2 := Point{5, 10}
pt2.Scale(2.0)
JSON Marshaling
The encoding/json package provides support for JSON encoding and decoding.
import "encoding/json"
var team []Person
jsonData, err := json.Marshal(team) // Compact JSON.
jsonData, err := json.MarshalIndent(team, "", " ") // Formatted JSON.
Interfaces
An interface type defines a set of method signatures. A type implements an interface implicitly by implementing its methods.
Defining an interface:
type Reader interface {
Read(data []byte) (n int, err error)
}
Embedding interfaces:
type Writer interface {
Write(data []byte) (n int, err error)
}
type ReadWriter interface {
Reader
Writer
}
Implementing an interface:
type Document struct {
content string
}
func (d *Document) Read(buf []byte) (int, error) {
copy(buf, []byte(d.content))
return len(d.content), nil
}
// Document now implements the Reader interface.
The empty interface:
var anything interface{} // Can hold a value of any type.
An interface value holds a pair: a concrete type and a value of that type (dynamic type and dynamic value).