go go/book/learninggo

Learning Go, Chapter 03

Arrays and Slices

Go - Arrays & Slices > Array vs Slices

Go has arrays. However, arrays are rarely used directly in Go.

Go Has only one dimensional arrays. but we can simulate multidimensional arrays.

Using [...] makes an array. Using [] makes a slice.

A slice is the first type you’ve seen that isn’t comparable. It is a compile-time error to use to see if two slices are identical or != to see if they are different. The only thing you can compare a slice with using is nil:

fmt.Println(x == nil) // true
 
x := []int{1,2,3}
y := []int{1,2,3}
 
fmt.Println(slices.Equal(x, y)) // prints true

Warning

The reflect package contains a function called DeepEqual that can compare almost anything, including slices. It’s a legacy function, primarily intended for testing. Before the inclusion of slices.Equal and slices.EqualFunc, reflect.DeepEqual was often used to compare slices. Don’t use it in new code, as it is slower and less safe than using the functions in the slices package.

append

append function used to grow slices.

var x []int
x = append(x,10,11)

Slices and Capacity in Go

  • A slice is a sequence of elements backed by an underlying array.
  • Each slice has:
    • Length: number of actual elements.
    • Capacity: number of elements it can hold before needing to grow.
  • When append is called:
    • If there’s room (length < capacity), the element is added directly.
    • If not, a new larger array is allocated by the Go runtime.
    • Existing elements are copied, new ones added, and the updated slice is returned.

Go Runtime and Slice Growth

  • The Go runtime handles memory management, garbage collection, and other low-level services.
  • It’s included in every Go binary, making distribution easier but increasing binary size (~2MB).
  • Growing a slice has a performance cost (allocation + copy + GC).
  • To reduce repeated allocations, the runtime increases capacity by:
    • Doubling it if current capacity < 256.
    • Using (current_capacity + 768) / 4 when capacity ≥ 256.
      • This results in growth rates that gradually reduce toward 25% as the slice gets larger.

make

The Difference between Nil and Empty Slices in Go

we can use clear(slicename) to emptying a slice. it’s sets all of the slice’s elements to their zero value. The length of the slice remains unchanged.

	s := []string{"hello", "gopher"}
	fmt.Println(s, len(s))
	clear(s)
	fmt.Println(s, len(s))
 
	l := []int{1, 2, 3}
	fmt.Println(l, len(l))
	clear(l)
	fmt.Println(l, len(l))
 
// [hello gopher] 2
// [ ] 2
// [1 2 3] 3
// [0 0 0] 3

zero value for a string is an empty string ""!

Go - Nil Slices and Empty Slices

Strings, Runes and Bytes

Go use sequence of bytes to represent a string. These bytes don’t have to be in any particular character encoding, but several Go library functions assume that string is composed of a sequence of UTF-8 encoded code points.

A string can be converted back and forth to a slice of bytes or a slice of runes.

var s string = "Hello, "
var bs []byte = []byte(s)
var rs []rune = []rune(s)
fmt.Println(bs)
fmt.Println(rs)
 
// [72 101 108 108 111 44 32 240 159 140 158]
// [72 101 108 108 111 44 32 127774]
Featurerunestring
TypeAlias for int32A sequence of bytes ([]byte)
RepresentsA single Unicode code pointA sequence of bytes (UTF-8 text)
SizeFixed: 4 bytesVariable: depends on number of bytes
ImmutabilityA rune is a value (mutable)Strings are immutable
Use caseFor working with individual characters, especially UnicodeFor whole words, sentences, etc.
Example'ĺ­—', 'A'"Hello", "ĺ­—ĺ…¸"
  • rune is an alias for the int32 type.
  • Go strings are sequences of bytes.
  • A Unicode character can be more than one byte in UTF-8 (1-4 bytes)
  • Indexing strings directly (s[0]) gives bytes, not characters.

Maps

map — Blog — Bitfield Consulting
How the Go runtime implements maps efficiently (without generics) | Dave Cheney

The map that’s built into Go is a hash map, or hash table

Grokking Algorithms, Chapter 05
[!video]- GopherCon 2016: Inside the Map Implementation - Keith Randall

m := make(map[string]int)
m["a"] = 1
m["b"] = 2
 
m // map[a:1 b:2]

Maps in Go are not Reference Types, But they Behave like them.

m := make(map[string]int)

The variable m is not the actual map data, it’s a pointer to a runtime structure(runtime.hmap) that holds the actual data.
So when you pass m to a function, you’re copying the pointer, not the whole map.
This means:

  • Multiple copies of m (in different variables or function arguments) point to the same underlying data.
  • If one copy modifies the map, **all see the change**.

Nil Maps and Empty Maps

Declare a variable nilMap which will eventually point to a map from strings to ints, but for now, don’t point it to anything(does not point to any underlying map structure yet).

var nilMap map[string]int
 
len(nilMap) // Return 0
 
nilMap["foo"] // Return 0
 
nilMap["newKey"] = 42 // 🚨 panic: assignment to entry in nil map

Following syntax creates an empty map. It safe for both reading and writing.

emptyMap := map[string]int{}
 
len(emptyMap) // Return 0
 
emptyMap == nil // Return false

If you know how many key-value pairs you plan to insert into a map, you can use make to create a map with a specific initial size.

ages := make(map[int][]string, 10)

The second argument is capacity hint, not a strict limit.
Go uses it to optimize initial allocation, it preallocates room for about that many keys.
This avoids early resizing and copying during the first few insertions.

You can check if they are equal to nil, but you cannot check if two maps have identical keys and values using == or differ using !=.

Map or Slice

You should use slices for lists of data when the data should be processed
sequentially or the order of the elements is important.
Maps are useful when you need to organize values using something other than an increasing integer value, such as a name

Using Map

totalWins := map[string]int{}
totalWins["Orcas"] = 1
totalWins["Lions"] = 2
fmt.Println(totalWins["Orcas"])
fmt.Println(totalWins["Kittens"])
totalWins["Kittens"]++
fmt.Println(totalWins["Kittens"])
totalWins["Lions"] = 3
fmt.Println(totalWins["Lions"])

Comma ok Idiom

m := map[string]int{
	"hello": 5,
	"world": 0,
}
 
v, ok := m["hello"]
fmt.Println(v, ok) // 5, true
 
v, ok = m["goodbye"]
fmt.Println(v, ok) // 0, false

Maps Operations

import (
	"fmt"
	"maps" // Requires Go 1.21+
)
 
func main() {
	// --- Original Map ---
	m := map[string]int{
		"hello": 5,
		"world": 10,
	}
	fmt.Println("Original map:", m)
 
	// --- Deleting a Key ---
	delete(m, "hello")
	fmt.Println("After delete:", m)
 
	// --- Clearing the Map ---
	clear(m)
	fmt.Println("After clear:", m, "Length:", len(m))
 
	// --- Comparing Maps ---
	a := map[string]int{
		"foo": 1,
		"bar": 2,
	}
	b := map[string]int{
		"bar": 2,
		"foo": 1,
	}
	c := map[string]int{
		"bar": 2,
		"foo": 3, // different value
	}
 
	fmt.Println("a == b:", maps.Equal(a, b)) // true
	fmt.Println("a == c:", maps.Equal(a, c)) // false
}

Implementing Set using Map

Implementing Sets in Golang

A set is a data structure.

  • No duplicate elements are allowed.
  • The order of elements is not guaranteed.
  • Sets are commonly used to test membership, eliminate duplicates, or perform set-theoretic operations like union, intersection, etc.
intSet := map[int]bool{} // empty map
 
vals := []int{5, 10, 2, 6, 7, 2, 1}
 
for _, v := range vals {
	intSet[v] = true
}
 
fmt.Println(len(vals), len(intSet))
fmt.Println(intSet[5])
fmt.Println(intSet[500])
if intSet[100] {
	fmt.Println("100 is in the set")
}

We wrote 11 values into intSet, but the length of intSet is 8, because you cannot have duplicate keys in a map. If you look for 5 in intSet, it returns true, because there is a key with the value 5. However, if you look for 500 or 100 in intSet, it returns false. This is because you haven’t put either value into intSet, which causes the map to return the zero value for the map value, and the zero value for a bool is false.

Some people prefer to use struct{} for the value when a map is being used to implement a set. (I’ll discuss structs in the next section.) The advantage is that an empty struct uses zero bytes, while a boolean uses one byte.

The disadvantage is that using a struct{} makes your code clumsier. You have a less obvious assignment, and you need to use the comma ok idiom to check if a value is in the set:

intSet := map[int]struct{}{} // Map Literal / Empty Map
vals := []int{5, 10, 2, 5, 8, 7, 3, 9, 1, 2, 10}
for _, v := range vals {
	intSet[v] = struct{}{}
}
if _, ok := intSet[5]; ok {
	fmt.Println("5 is in the set")
}
  • struct{} is the zero-size type in Go.
  • struct{}{} is the literal syntax for “create a value of that type”.
var y struct{} // y is of type struct{}
y = struct{}{} // valid value assigned
ExpressionWhat it isUse case
struct{}A typeFor declaring variables or map types
struct{}{}A value of that typeFor initializing or assigning

Structs

👉 Go - Types & Structs > Struct

Go doesn’t have class, because it doesn’t have inheritance.
Also note that unlike in map literals, no commas separate the fields in a struct declaration.

type person struct {
	name string
	age int
	pet string
}
 
bob := person{}
 
bob.name = "Bob"
fmt.Println(bob.name)
 
julia := person{
	"Julia",
	40,
	"cat",
}
 
// It allows you to specify the fields in any order, and you don’t need to provide a value for all fields. 
// Any field not specified is set to its zero value.
beth := person{
	age: 30,
	name: "Beth",
}

Unlike maps, there is no difference between assigning an empty struct literal and not assigning a value at all. Both initialize all fields in the struct to their zero values. There are two styles for a nonempty struct literal.

Anonymous Struct

Anonymous structs are handy in two common situations. The first is when you translate external data into a struct or a struct into external data (like JSON or Protocol Buffers). This is called unmarshaling and marshaling data.

// Define an anonymous struct type and declare a variable `person` of that type.
// The struct has three fields: name, age, and pet.
var person struct {
	name string
	age  int
	pet  string
}
 
// Assign values to the fields of the `person` struct.
person.name = "bob"   // Set the name field to "bob"
person.age = 50       // Set the age field to 50
person.pet = "dog"    // Set the pet field to "dog"
 
// Define and initialize a new struct `pet` using a composite literal.
// This struct is also anonymous and has two fields: name and kind.
pet := struct {
	name string
	kind string
}{
	name: "Fido",      // Set the name of the pet to "Fido"
	kind: "dog",       // Set the kind of the pet to "dog"
}

Struct with Tags

Tags are metadata added to struct fields, often used for defining JSON serialization rules and other data formatting.

type Person struct {
    Name  string `json:"name"`
    Age   int    `json:"age"`
}
 
func main() {
    person := Person{"Alice", 30}
    data, _ := json.Marshal(person)
    fmt.Println(string(data)) // Output: {"name":"Alice","age":30}
}

Nested Structs

In a nested struct, you include one struct as a field within another struct. This means that to access the fields of the nested struct, you need to use the full path (parent.child.field). This approach does not support inheritance-like behavior.

package main
 
import "fmt"
 
type Address struct {
	City    string
	Country string
}
 
type Person struct {
	Name    string
	Age     int
	Address Address  // Nested struct (Address is a field)
}
 
func main() {
	p := Person{
		Name: "Alice",
		Age:  30,
		Address: Address{
			City:    "New York",
			Country: "USA",
		},
	}
 
	fmt.Println("Name:", p.Name)
	fmt.Println("City:", p.Address.City)   // Accessing nested field
	fmt.Println("Country:", p.Address.Country)
}
  • Access: You need to access fields of Address through p.Address.
  • No Inheritance-like Behavior: Address fields are not directly accessible on Person.

Embedded Structs

In an embedded struct, you include a struct type without naming it as a field. This is called embedding and it promotes the embedded struct’s fields and methods to the outer struct. This allows inheritance-like behavior because you can access the fields and methods of the embedded struct directly from the outer struct.

package main
 
import "fmt"
 
type Address struct {
	City    string
	Country string
}
 
type Person struct {
	Name    string
	Age     int
	Address           // Embedded struct (no explicit field name)
}
 
func main() {
	p := Person{
		Name: "Alice",
		Age:  30,
		Address: Address{
			City:    "New York",
			Country: "USA",
		},
	}
 
	fmt.Println("Name:", p.Name)
	fmt.Println("City:", p.City)      // Accessing embedded field directly
	fmt.Println("Country:", p.Country)
}
  • Direct Access: Fields of Address can be accessed directly from Person without needing the Address field name.
  • Inheritance-like Behavior: This allows Person to “inherit” fields and methods from Address.

Function as a Field in Golang Structure

package main
import "fmt"
type Person struct {
    Name     string
    Greet    func(string) string
    Farewell func() string
}
func main() {
    person := Person{
        Name: "C",
    }
    // Assign the greet and farewell functions after the person is defined
    person.Greet = func(greeting string) string {
        return greeting + ", " + person.Name
    }
    person.Farewell = func() string {
        return "Goodbye, " + person.Name
    }
    // Call the function fields
    fmt.Println(person.Greet("Hello"))
    fmt.Println(person.Farewell())
}

Pointer to Structs

package main
import "fmt"
 
func main() {
 
  // declare a struct Person
  type Person struct {
    name string
    age int
  }
 
  // instance of the struct Person
  person1 := Person{"John", 25}
 
  // create a struct type pointer that
  // stores the address of person1
  var ptr *Person
  ptr = &person1
 
  // print struct instance
  fmt.Println(person1)
 
  // print the struct type pointer
  fmt.Println(ptr)
}
 var ptr = &Person{
	name: "John",
    age: 25,
 }

Converting Struct

Learning Go, Page 64

Summary

ExpressionMeaning
struct{}Empty struct type
struct{}{}Empty struct value/literal
map[string]struct{}Memory-efficient set
chan struct{}Signal-only channel (zero memory)

struct{}

var x struct{}        // Declare variable of empty struct type
y := struct{}{}       // Create and assign an empty struct literal
  • Uses no memory
  • Is often used for signals, markers, or set membership without storing data

for signling

done := make(chan struct{})
 
go func() {
    time.Sleep(time.Second)
    done <- struct{}{} // signal
}()
 
<-done // wait for signal
fmt.Println("Finished")

In the Map

visited := make(map[string]struct{})
visited["google.com"] = struct{}{} // value doesn't matter
 
if _, ok := visited["google.com"]; ok {
	fmt.Println("Already visited")
}