Posts

Showing posts from August, 2020

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,...

For loop

  The Go   for   loop is similar to—but not the same as—C's. It unifies   for   and   while   and there is no   do-while . There are three forms, only one of which has semicolons. // Like a C for for init; condition; post { } // Like a C while for condition { } // Like a C for(;;) for { } Short declarations make it easy to declare the index variable right in the loop. sum := 0 for i := 0; i < 10; i++ { sum += i } If you're looping over an array, slice, string, or map, or reading from a channel, a  range  clause can manage the loop. for key, value := range oldMap { newMap[key] = value } If you only need the first item in the range (the key or index), drop the second: for key := range m { if key.expired() { delete(m, key) } } If you only need the second item in the range (the value), use the  blank identifier , an underscore, to discard the first: sum := 0 for _, value := range array { sum += value }

Control statement

  In Go a simple   if   looks like this: if x > 0 { return y } Mandatory braces encourage writing simple  if  statements on multiple lines. It's good style to do so anyway, especially when the body contains a control statement such as a  return  or  break . Since  if  and  switch  accept an initialization statement, it's common to see one used to set up a local variable. if err := file.Chmod(0664); err != nil { log.Print(err) return err } In the Go libraries, you'll find that when an  if  statement doesn't flow into the next statement—that is, the body ends in  break ,  continue ,  goto , or  return —the unnecessary  else  is omitted. f, err := os.Open(name) if err != nil { return err } codeUsing(f) This is an example of a common situation where code must guard against a sequence of error conditions. The code reads well if the successful flow of control runs down the pa...

Naming convention

  Interface names By convention, one-method interfaces are named by the method name plus an -er suffix or similar modification to construct an agent noun:  Reader ,  Writer ,  Formatter ,  CloseNotifier  etc. MixedCaps Finally, the convention in Go is to use  MixedCaps  or  mixedCaps  rather than underscores to write multiword names. Getters It's neither idiomatic nor necessary to put  Get  into the getter's name. If you have a field called  owner  (lower case, unexported), the getter method should be called  Owner  (upper case, exported), not  GetOwner . The use of upper-case names for export provides the hook to discriminate the field from the method. A setter function, if needed, will likely be called  SetOwner . Both names read well in practice: owner := obj.Owner() if owner != user {     obj.SetOwner(user) } They even have semantic effect: the visibility of a name outside a package i...

;

The rule is this. If the last token before a newline is an identifier (which includes words like  int  and  float64 ), a basic literal such as a number or string constant, or one of the tokens break continue fallthrough return ++ -- ) } the lexer always inserts a semicolon after the token. This could be summarized as, “if the newline comes after a token that could end a statement, insert a semicolon”. A semicolon can also be omitted immediately before a closing brace, so a statement such as go func() { for { dst <- <-src } }() needs no semicolons. Idiomatic Go programs have semicolons only in places such as  for  loop clauses, to separate the initializer, condition, and continuation elements. They are also necessary to separate multiple statements on a line, should you write code that way. One consequence of the semicolon insertion rules is that you cannot put the opening brace of a control structure ( if ,  for ,  switch , or  select ...