Notes: GoLang - Maps vs Slices and Using Slice as buffer

Notes: GoLang - Maps vs Slices and Using Slice as buffer

Maps vs Slices

  • In go maps are refernce oriented. If you pass a map to a function as argument, any changes to that map inside the function will reflect to original function as well.
package main

import (
	"fmt"
)

func main() {
	var testMap = make(map[string]string)
	testMap["lang"] = "go"
	fmt.Println("Before updating the map")
	fmt.Println("-------------")
	for key, val := range testMap {
		fmt.Println(key, val)
	}

	_ = updateMap(testMap)
	fmt.Println()
	fmt.Println("Before updating the map in another function")
	fmt.Println("-------------")
	for key, val := range testMap {
		fmt.Println(key, val)
	}
}

func updateMap(testMap map[string]string) map[string]string {
	testMap["type"] = "compiled"
	return testMap
}

  • The main reason behind this is, in Go maps are implemented as pointer to structs
  • In the runtime Go implements a struct based on the map.
    Ref: https://dave.cheney.net/2018/05/29/how-the-go-runtime-implements-maps-efficiently-without-generics
  • Passing a map to function means that you are copying a pointer i.e address/reference of the map, so any changes to map will happen at the memory of the map.
  • While designing APIs, having maps as input or return values is bad choice due to we dont what that map contained.
  • As you pass the map and these are something similar to updated by reference, in any where of your code, if you update the map, there is no of knowing unless we inspect the code.
  • So during the cases where map implementation needed, we can leverage struct, as to struct update will not have any thing like reference, so you must return the struct to know the changes
package main

import (
	"fmt"
)

type Student struct {
	name string
	num  int
}

func main() {

	s := Student{"student1", 111}
	fmt.Println(s.name)
	newName := "student2"
	
	// case 1 not caputering it
	_ = updateStudentName(newName, s)
	fmt.Println(s.name)
	
	// case 2 caputering it
	s = updateStudentName("Student3", s)
	fmt.Println(s.name)

}

func updateStudentName(newName string, s Student) Student {
	s.name = newName
	return s
}
  • When it come to slices, they are updated by reference at one case and they are not yet another.
  • To put it in simple words, under criteria of length and capacity, if you update a slice and its copy as long as it maintaines the same memory location pointer, both will get updated.
  • but if some append operation which might change length of slice, then Go runtime will try to use existing slice capacity else create a new memory location and thats where the break will happen with orignial slice and new slice in terms of memory location.
  • if go trying to use existing slice reserved capacity, then it will be a shared scenario. Slice1 and Slice2 will share what ever the data common and new data appended to slice2 will be hidden from slice1, and length of slice1 also will not change.
package main

import (
	"fmt"
)

func main() {

	slice1 := make([]int, 5, 5)
	slice1 = []int{1, 2, 3, 4, 5}
	fmt.Println(slice1)

	slice2 := slice1
	fmt.Println(slice2)

	// update by changing existing
	slice2[0] = 100
	fmt.Println(slice1)
	fmt.Println(slice2)

	// now do append
	slice2 = append(slice2, 101)
	fmt.Println(slice2)
	fmt.Println(slice1)
}

Slices As Buffers

  • So as long as you replace data to slice, it size and length wont get change.
  • so the memory location wont change
  • every time old data processing completed, new data get placed into the same memory location.
  • so garbage collection is only once and that is the end of the complete Read operation.
  • to read from file, we create the famous byte buffer and every time read the chunk of byte from the sources
  • then process it
data := make([]byte, 100)
count, err := file.Read(data)
// count is length of chunk that we read
// err returns if any 

0 comments:

Post a Comment