Posts

Arrays

  Arrays primarily they are a building block for slices,  There are major differences between the ways arrays work in Go and C. In Go, Arrays are values. Assigning one array to another copies all the elements. In particular, if you pass an array to a function, it will receive a  copy  of the array, not a pointer to it. The size of an array is part of its type. The types  [10]int  and  [20]int  are distinct. The value property can be useful but also expensive; if you want C-like behavior and efficiency, you can pass a pointer to the array. func Sum(a *[3]float64) (sum float64) { for _, v := range *a { sum += v } return } array := [...]float64{7.0, 8.5, 9.1} x := Sum(&array) // Note the explicit address-of operator But even this style isn't idiomatic Go. Use slices instead.

Make

  Allocation with  make The built-in function   make(T,  args )   serves a purpose different from  new(T) .  It creates slices, maps, and channels only, and it returns an  initialized  (not  zeroed ) value of type  T  (not  *T ).  The reason for the distinction is that these three types represent, under the covers, references to data structures that must be initialized before use .  A slice, for example, is a three-item descriptor containing a pointer to the data (inside an array), the length, and the capacity, and until those items are initialized, the slice is  nil .  For slices, maps, and channels,  make  initializes the internal data structure and prepares the value for use . For instance, make([]int, 10, 100) allocates an array of 100 ints and then creates a slice structure with length 10 and a capacity of 100 pointing at the first 10 elements of the array. (When making a slice, the capac...

Constructors

  Constructors and composite literals Sometimes the zero value isn't good enough and an initializing constructor is necessary, as in this example derived from package  os . func NewFile(fd int, name string) *File { if fd < 0 { return nil } f := new(File) f.fd = fd f.name = name f.dirinfo = nil f.nepipe = 0 return f } There's a lot of boiler plate in there. We can simplify it using a  composite literal , which is an expression that creates a new instance each time it is evaluated. func NewFile(fd int, name string) *File { if fd < 0 { return nil } f := File{fd, name, nil, 0} return &f } Note that, unlike in C, it's perfectly OK to return the address of a local variable;  the storage associated with the variable survives after the function returns. In fact, taking the address of a composite literal allocates a fresh instance each time it is evaluated, so we can combine these last two lines. r...
Allocation with  new Go has two allocation primitives, the built-in functions  new  and  make .  it does not  initialize  the memory, it only  zeros  it. That is,  new(T)  allocates zeroed storage for a new item of type  T  and returns its address, a value of type  *T . In Go terminology, it returns a pointer to a newly allocated zero value of type  T . Since the memory returned by  new  is zeroed, it's helpful to arrange when designing your data structures that the zero value of each type can be used without further initialization. This means a user of the data structure can create one with  new  and get right to work.  For example, the documentation for  bytes.Buffer  states that "the zero value for  Buffer  is an empty buffer ready to use." Similarly,  sync.Mutex  does not have an explicit constructor or  Init  method. Instead, the zero value for...

Defer

  Defer Go's  defer  statement schedules a function call (the  deferred  function) to be run immediately before the function executing the  defer  returns. It's an unusual but effective way to deal with situations such as resources that must be released regardless of which path a function takes to return. The canonical examples are unlocking a mutex or closing a file. // Contents returns the file's contents as a string. func Contents(filename string) (string, error) { f, err := os.Open(filename) if err != nil { return "", err } defer f.Close() // f.Close will run when we're finished. var result []byte buf := make([]byte, 100) for { n, err := f.Read(buf[0:]) result = append(result, buf[0:n]...) // append is discussed later. if err != nil { if err == io.EOF { break } return "", err // f will be closed if we return here. } } ...

Functions

Multiple return values One of Go's unusual features is that functions and methods can return multiple values.  func (file *File) Write(b []byte) (n int, err error) Named result parameters The return or result "parameters" of a Go function can be given names and used as regular variables, just like the incoming parameters. When named, they are initialized to the zero values for their types when the function begins; if the function executes a  return  statement with no arguments, the current values of the result parameters are used as the returned values. The names are not mandatory but they can make code shorter and clearer: they're documentation. If we name the results of  nextInt  it becomes obvious which returned  int  is which. func nextInt(b []byte, pos int) (value, nextPos int) { Because named results are initialized and tied to an unadorned return, they can simplify as well as clarify. Here's a version of  io.ReadFull  that uses them we...

Switch

  Switch The expressions need not be constants or even integers, the cases are evaluated top to bottom until a match is found, and if the  switch  has no expression it switches on  true . It's therefore possible—and idiomatic—to write an  if - else - if - else  chain as a  switch . func unhex(c byte) byte { switch { case '0' <= c && c <= '9': return c - '0' case 'a' <= c && c <= 'f': return c - 'a' + 10 case 'A' <= c && c <= 'F': return c - 'A' + 10 } return 0 } There is no automatic fall through, but cases can be presented in comma-separated lists. func shouldEscape(c byte) bool { switch c { case ' ', '?', '&', '=', '#', '+', '%': return true } return false }   break  statements can be used to terminate a  switch  early. Sometimes, though,...