GO(lang) for programmers

home

 

GO, sometimes called golang, is Google's language. It's famous for easy communicating threads. A big surprise, it uses explicit pointers and (mostly) explicit stack/heap object creation. It's pretty funny to read explanations of this by people who have only ever used reference types. It's also garbage collected. Snytax-wise it's C-style syntax with the usual modern tricks -- optional line-endings, slices, and so on.

Formatting, weirdness

{} surrounds blocks, in C-like fashion. Comments are the usual // line comments or /* begin-end comments */. GO has the new-style optional semi-colon rules, but in a funny way. For example, identifiers end a line, even if it was clearly meant to continue.

It's an error to declare or include anything you don't need. Arrg! Make it a warning. The "don't care" variable underscore(_) has an official status to get around this.

Declaring, types

All declarations put the type after the identifier. The usual colon isn't there, just a space. Simple declarations begin with var:

var n1 int
var n2 string = "abc"
var n3, n4 float32

Int/float types are supposed to have the size in bits, like int32 or float64, but int is a shortcut (float has no such shortcut). Basic types are: int, uint8 (aka byte), float32, float64 and string. Characters are called runes (an aliased uint32).

Strings can be raw, using backticks, or normal with double-quotes:

w = `abc`; w="abc" // same
w = `a\nb\nc` // len(w) is 7
w = "a\nb\nc" // length 5

Everything is auto-initialized. var n1 int sets n1 to 0, strings are set to "", and so on

You're allowed multi-assign, which can also be used in declarations. But these aren't tuples and the lengths must math:

var a,b int = 10,12 // a=10, b=12, both are declared
// also legal for a non-declare assign:
a,b=4,5 // a=4, b=5

The special inferred type declare shortcut uses a :=. This is very common:

n:=6 // var n int = 6
a,b := 4,2

b:=2 // error -- b was already declared 

string formats

Built-in Printf can use %v as a stand-in for anything. We also get the old C-like %4.2f and so on, and a new one for the type:

fmt.Printf("show me %v and %v", 5, "arf") // show me 5 and arf
fmt.Printf("val=%4.2f", 7.0) // 7.00

fmt.Printf("type of n is %T", n)

Convert to string with the old Sprintf:

w=fmt.Sprintf("%v x %v",5, 9) // "5 x 9"

Arrays

Arrays are very restrictive in GO. They're a value-type and the length is part of the type. In other words, a function can't take an array input -- it could take a length 10 array. In GO slices are used instead of arrays (GO slices can be backed by an array variable, but don't have to be!)

Arrays look like name [size]type:

var A [5]int // a length 5 array of ints
var aLen=len(A) // 5. The same len as for any list-like thing

Array constants have a fun shortcut: list indexes:value pairs. Unused indexed get default values, as usual. Here notice how "rat" has no index, so goes directly after the previous "cat":

var A [6]string // A has five ""'s
// assign
A = [6]string{1:"cat", "rat" 4:"dog"}
// {"","cat","rat","","dog",""}

This might be good for quick sparse arrays for testing, but otherwise seems like too much syntax for the gain.

You're not allowed to leave the size blank in an array literal, but can use triple-dot instead:

A = [...]string{"hot","cold"} // the system fills in the [2]

slices

An apparent array declaration with no size, simply [], is a slice. GO doesn't have the cool [:-1] from-the-end rule, but at least has the missing begin/end shortcut:

// two arrays:
var A [3]int {1,2,3}
var B [5]int {3,6,7,2,5}
A=B // error, A has type [3]int

var C []int // a slice (currently length 0)
C = A[0:3] // all of A
C = B[2:] // last three #'s in B

The slice goes to just before the second index. [2:3] is only the element at [2]. Slice indexes always start at 0, regardless of the start in the underlying array (I can't believe I have to write that, but it's not true for all languages).

Slices don't need to be backed by a declared GO array. Those are GO's version of a real array-backed list class. Here slice Nums creates and grows a backing array as needed:

Nums := []int // length 0 slice
append(Nums, 3) // Nums is {3}
append(Nums, 7,9,12) // adds all: {3,7,9,12}

append(Nums, []int{4,5,6}...)
// ... is a hack to unpack a slice into (only) variadic parms

Append works the usual way: it adds the new item to the underlying array if it can, otherwise it creates a new one with double the size. Ex (this is very odd)

// the real array has size 4 (all 0's):
var N [4]int = [4]int{}
// a slice of only the 1st three:
s1 := N[:3]
fmt.Printn("len=%v cap=%v", len(s1), cap(s1)) // len=3 cap=4
s1[0]=9 // changes backing array N[0]
append(s1, 5) // has room, N[3] is 5
append(s1, 6) // s1 now points to a new array with len=8
s1[0]=12 // doesn't change N, s1 no longer points to it

There's no remove function. You can use the terrible append(N[:index], N[index+1:]...) to slam the two halves together. Ick.

maps

Maps (hash tables) in GO work the usual way. The definition is strange, but consistant with the rest of GO:

var m1 map[string]int // type is string->int
// NOTE: m1 is nil

// assign using a map constructor:
m1 = map[string]int{"cow":1,"dog":2}

// cheap way to declare a usable empty map:
m2 := map[string]int{} // {} constructs a map, with no items

You may also see people getting a usable empty map with m3:= make(map[string]int). It's nothing special, just an alternate way.

Maps work as usual, except checking for "exists" uses the tuple trick to return whether it worked:

// the usual assign and read:
m1["frog"]=7
var n int = m1["goat"]

// special exists check using tuple assign:
val, success = m1["pony"] // 0 false (assuming no ponies)

val = m1["pony"] // also legal, but gives 0 if not found

Normally you have to catch all returned values in a tuple, but map look-ups are an exception. You can skip catching error bool.

structs

Declare structs the usual way. There's no public/private. GO uses package visbility. There's also no default constructor, but you can builtin "set all fields" onoes. GO assumes you'll either use a creatin function, or you like 0's:

// define the struct:
type Point struct { x,y float32 }

// declare as usual:
var p1 Point // both are 0

// create a literal like a constructor, listing all raw fields:
p1 = Point{7,4}

// or list fields by name:
p1 = Point{y:5,x:9} // by name, any order
p1 = Point{x:5} // as usual, y is 0

casting

Casting uses C-like syntax TYPE(val), for example int(f) drops the fraction of a float. There are some funny rules for characters. They don't exist. Instead we get an unsigned int. Conversely, string(uint) convert to the single ASCII (unicode) character:

// instead of a char type, we have uint:
n := "a"[0]; fmt.Printf("type is %T, val=%v", n, n)
// type is uint8, val=97

fmt.Println(string(b)) // "a" (not "97")

The correct way to cast a number into a string is w=fmt.Sprintf("%v",n)

Misc

includes, entry point, files

Every GO file begins with the "package" name, which it uses to import them later. I think they may need to be java-style in a file with the same name. Or maybe a popular GO tool requires this.

include statements use import. Things to include are listed C-style in quotes, with the path (if needed):

import "fmt"

import ( // parens for multiple
  "time"
  "sync"; "sync/atomic"
)

This imports the package name (in other words, you'll need the full fmt.Println to use it). To pull the contents into your scope, add a dot before:

import . "fmt"

Println("don't need fmt")
fmt.Println("in fact, this is an error -- what's fmt?")

There may be a way to import only certain items -- such as only Printf -- not sure.

Importing something you don't use is an error (arrg!)

The main entry point is also C-style: func main(). Command line args are a bit funny. You don't change the signature of main. Instead GO puts them in its os.Arg:

package main // main is the official package name for entry point

import "os" // needed to read from os.Args

func main() { // no change, args are sent elsewhere
  numArgs := len(os.Args)
  progName :=os.Args[0] // as usual, 1st input is program name

Public/Private

Everything is package scope, even member variables. To make something public for exporting, start the name with a capital letter (in other words, to make something private-to-a-package in GO, start it with a lower-case latter).

typedefs, aliases

GO has C's old typedef system: you can rename a type, or give a shorter name for a compound type, and it counts as being different. Use type NEW_NAME EXISTING_TYPE. Or multiples with parens and semi-colons:

// alternate float32:
type loudType float32

// 2 alternate ints:
type ( catWeight_t float32; catAge int )

var ca1 catAge;
ca1 = 8
ca1 += 4

// NOTE: ca1 and int variables can't mix:
n1 := 4; ca1+=n1 // error

Oddly, this doesn't treat compound types as different, only simple names. For example, below an sList is identical to []string, but not to []someStringAlias:

// make an alias for "slice of strings"
type sList []string
// check that it works:
W1 := sList{"cat","dog"} // a slice literal, using the new sList

// check sList and []string can mix:
var W2 []string = W1 // legal

// but not if the underlying type is a non-string string:
type altString string
var W3 []altString; W3=W1 // error: []string isn't []altString

I'm not sure if this behaviour is useful, or simply the best way they knew how to implement it.

Adding = to a typedef makes it merely an alias. The difference is that they count as the other type (I think they're replaced at compile-time):

type size_t=float32
// can freely mix size_t and float32

Offially these are only meant as a hack while re-jiggerring programs.

IF's

GO if's are mostly normal. The ()'s around the test are optional, but {}'s around the blocks are required. An else can't start a line (due to the funny semicolon rules). A special rule: you're allowed to put one statement at the start, ending in a semicolon. These are often := declares which are in the IF's local scope, so that's nice:

// simple ifs:
if a==b { sameVal=true }
if n==1 || n==4 || n==8 { n+=5 }

// with an else:
if (a<10) { w="small"
} else { w="big" }

// the local var rule in a cascading if (all use local n=6)
var n int = 99 // global
// declares local n:
if n:=6; n>10 { 
  fmt.Println("one %v\n", n) // local n=6
} else if !(n<20) {
  fmt.Println("two %v\n", n) // local n is still 6
} else { fmt.Println("three %v\n", n) }

fmt.Println(n) // back to global n: 99

loops

GO uses the for keyword for it's 3 types of loops: a normal while loop, a normal for loop, and a fancy foreach loop. Notice how none have ()'s around the test:

// while loop:
for n<10 { n+=1; val+=n }

// standard for loop:
// note: this also declares n
for n:=0;n<10;n++ { fmt.Println(n) }

// fancy foreach loop
for i,ch := range "abcdef" { ... } // 0 a, 1 b, 2 c ...

The := range is required in the foreach style. It can use 1 or 2 loop variables. The first is the index and the second is the value: for i := range "abcde" runs i 0 to 4.

If you only want the value (a traditional foreach) you'll need to use an underscore for the index (or else trigger a "must use i" error):

for _,val := range myArray { fmt.Printf("%v ",val) }

functions

GO purposely doesn't have optional parameters and overloading (which also means no operator overloading). It doesn't have template functions!! That's a huge problem.

GO functions begin with keyword func. As a shortcut you may list several parameter names with the type once at the end. The return-value goes at the end -- it should be in parens, but that's only required if you return a tuple (they can return tuples). For no return value, leave it blank:

func addsOne(n int) int { return n+1 }

// input is 2 ints and a string:
// NOTE: also legal is (a int, b int, w string)
func sumDisplay(a,b int, w string) string {
  return w+fmt.Sprint(a+b)
}

Arrays are always passed as slices (you can pass a real GO array, but the size-is-part-of-type makes it pointless):

// this takes a slice as input:
func maxIndex(N []int) int {
  maxi, maxVal = -1, -99
  for i,v := range N {
    if i==0 || v>maxVal { maxi=i; maxVal=v }
  }
  return maxi
}

// to call it, get a full slice of your array:
i1 = maxIndex(myArray[:])

GO functions can returning multiple parameters in the usual way (but GO does not have real tuples). They will be to be in parens in the header:

func sorted(a, b int) (int,int) {
  if a<b { return a,b }
  return b,a
}

n1,n2 := sorted(7,4)

A very silly rule: if you name the return values (in the header), then they are implicitly returned. Just assign to them and use an empty return:

// result is declared in the header, simply assign to it:
func maximum(n1, n2 float32) (result float32) {
  if n1>n2 { result=n1 } else { result=n2 }
  return
}

member functions

User-made types can only have pretend member functions (like C# extension functions). The "calling" type goes in front, but that's just syntactic sugar. They're regular functions:

type struct Point { x,y int }

func (p Point) toString() string {
  return fmt.Printf("(%v, %v)", p.x, p.y)
}

w := p1.toString()

You can also write these for your alternate versions of basic types:

type wholeNumber int

// defines wholeNumber.isEven():
func (n wholeNumber) isEven() bool { return n%2==0 }

var cats wholeNumber = 7;
evenCats := cats.isEven()

You're allowed to "overload" these (reuse the same names). You pretty much have to, since GO has interfaces which depend on same-name member functions.

first class and anonymous functions

With type inference, a "function pointer" is easy:

sd := sumDisplay;
w := sd(5,6,"sum is ") // same calling syntax

Even without the shortcut, the syntax rules are the usual:

// the long way of declaring a function var:
var sd func(int, int, string) string = moo

Using a function as an argument:

// this takes a list, string, and a compare function:
func find(W []string, w2 string, comp func(string,string) bool) int {
  for i,v := range W { if comp(w2,v) {return i} }
  return -1
}

// using it:

// a anonymous compare function:
sameLen := func(w1, w2 string) bool { return len(w1)==len(w2) }

b:=find(myStringList, "cow", sameLen)

pointers

GO uses explicit pointers, not the Reference Type system most modern languages use. Like C, *int is a pointer, *p follows p, and &n creates a pointer to n. null in GO is nil:

var n int = 8
var iPtr *int = &n
(*iPtr)+=7 // n is 15
iPtr=nil // null is nil

GO adds a modern shortcut: pointers to structs can skip the deference: dogPtr.name is a shortcut for (*dogPtr).name):

d1 := Dog{"spot",8}
dPtr := &d1
(*dPtr).name+="ted"
dPtr.age+=1

Other than that, they're just pointers. For example, slices are a start, length and a pointer to the underlying array. Passing a slice to a function copies those. We can change the underlying array through the pointer, as usual, but not the slice values. To do that, pass a pointer:

func changeSlice(S []int) {
  S[0]=21 // follows pointer to underlying array

  // adds 34 to underlying array,
  // but only changes slice size in our local copy
  S=append(S,34)
}

A:=[3]int{} // {0,0,0,0,0} array
s1:=A[1:3] //    {0,0} slice of A

changeSlice(s1)
// A is now {0,21,0,34,0}
// s1 is now  {21,0} -- length is still 2

The above would works fine using standard pointer passing. Also notice how star has lower precedence so we need ()'s:

func addToSlice(S *[]int) { (*S)[0]=12; *S=append(*S,34) }

addToSlice(&s1)
// s1 now grows to {12,0,34}

new, make

The craziest rule here is that GO likes it when you return pointers to going-out-of-scope local variables. It considered a shortcut to using new. This is legal and idiomatic:

// returns a reference to a local variable:
func refToLocal() *Point {
  var p Point; p=Point{4,9};
  return &p
}

p := refToLocal(); // the local has been moved to the heap, for p
n:=(*p).y // testing, no deference error, n is 9

new works as usual -- it creates the object on the heap and returns a pointer:

p := new(int) // p is a *int, *p is 0
p := new([5]string) // p points to {"","","","",""}

new has a hacky shortcut for constructed values (structs and arrays and slices): use an & to get a pointer to it. The same as with returning pointers to local, GO is forced to place the about-to-be-destroyed item on the heap:

p1:=&amo;Point{4,7} // p1 is a *Point
var p2 *Point = &Point{}
a1:=&[5]string{} // a1 is a *[5]string

// tricky. 4:"cow" forces array to have len-5,
// leading empty [] gives us a full len-5 slice:
s1:=&[]string{4:"cow"}

Notice how the "input" is a constructed item, not a type. So this trick doesn't work with int, string and so on, since they can't be constructed and new won't take literals (p:=&8 isn't legal).

make is lumped in with new, but is nothing like it. It's a special function required to create channels (make(chan int)), which can also create slices and maps. It creates a value, not a reference, just because:

c1 := make(chan string) // only way to get a channel

// quick way to make a [:10] slice with room for 20 total: 
s1 := make([]int, 10, 20)

// silly way to create a map w/make:
m1 := make(map[string]int)
// same as m1:= map{string]int{}

Again, note s1 and m1 aren't pointers (and neither is c1)

returning functions, closures

Returning functions w/closures works just fine. A basic curried add:

func curryAdd(a int) (func(int) int) {
  return func(b int) int { return a+b }
}

// using it:
add4 := curryAdd(4) // add4 is a function that adds 4
n := add4(93) // n is 97

interfaces

GO has "weak" interfaces: structs (or anything else) don't need to register themselves to the interface. Instead, GO interfaces work with anything that has the correct signatures. In this case Dog and Cat each have a show() member function, so both implement the interface hasShow and can be treated as such:

type Dog struct { name string; age int }
type Cat struct { name string; age int }

func (d Dog) show() { fmt.Printf("%v, %v year\n", d.name, d.age) }
func (c Cat) show() { fmt.Printf("%v, %v year\n", c.name, c.age) }

type hasShow interface { show() }

Then we can write a normal function taking a hasShow object:

func multiShow(hs hasShow, times int) {
  for i:=0; i<times; i++ { hs.show() }
}

hasShow( Dog{"abc",5}, 2) // displays that dog twice

var c1 Cat; c1.name="Princess"; c1.age=6
hasShow( c1, 4) // displays Princess 4 times

We can also declare variables of type hasShow. Here's a little dance to show value vs. pointer:

d1 := Dog("ralph",3)
var hs hasShow = d1 // legal, a _copy_ of d1
d1.name="will not be reflected in hs"
hs.show() // age is 3 in this copy

You can use interfaces as pointers without declaring them as pointers. GO encourages that:

var hs hasShow = &d1 // assigning a pointer to an interface type

// testing hs's pointerness:
d1.age=999
hs.show() // yes, d1 points to hs, age is 999

// or pass a pointer-to-interface where it wants an interface:
multiShow(&d1, 2) // works: d1 or &d1, system handles either

You can declare pointers to interfaces, but it's discouraged.

Asynch, producer/consumer vars

GO functions

Calling a function with keyword go in front turns it into a asynchronous function -- the caller won't wait for it to finish. Calling a function which doesn't wait (block) this way is pointless. But if a function blocks, you're allowed to call it either way. Here we call slowPrint without waiting, then again with waiting:

func slowPrint(w string) {
  time.Sleep(Time.Duration(1000000000) // one second
  fmt.Println(w)
}

  go slowPrint("sp1") // we immediately move to next line
  fmt.Println("A")
  slowPrint("sp2") // wait 1 second for this to finish
  fmt.Println("A")

// output: A (one second delay) sp1 sp2 A  

The input to Sleep is a uint64 in nanoseconds (one billionths), but it's officially type time.Duration. A common hack is using Sleep(1000*time.Millisecond): 1000 has no type so converts to type time.Duration, and time.Millisecond is simply a million (milliseconds per nanasecond).

For measuring we get time.Now() and time.Since(TIME_VAR). For example. this loop runs 40 times over 4 seconds:

t1 := time.Now()
// loop runs until 4 seconds have passed:
for time.Since(t1) < time.Duration(4000000000) {
  fmt.Println(int64(time.Since(t1))) // 100,000,000
  fmt.Println(time.Since(t1)) // 100ms
  time.Sleep(100*time.Millisecond)
}

Asynch variables

GO has two sorts auto-blocking communication variables, which it calls channels. The simplest is a buffer. Create it with make and give it a size (even 1). Add and remove items using the special <- syntax:

syncInt := make(chan int, 10) // size 10 buffer

syncInt <- 64 // send to "channel"
syncInt <- -47 // send some more, it holds 10

var n = <-syncInt // pulls out the 64

The useful thing about these is they block when needed -- reading from an empty channel, or sending to a full one.

The other type of channel always blocks, even when sending to it, and forces a synch between the threads (one of both created with go). Here a thread attempt to write to an empty channel, but is forced to wait until the reader is ready:

// sends an int to a channel then tells us:
func slowSend(intChan chan int, sendMe int) {
  intChan <- sendMe // blocks until someone tries to read!
  fmt.Println("sent")
}

  // start our function sending us a 64:
  iChan := make(chan int)
  go slowSend(iChan, 64)
  fmt.Println("this comes first -- function is blocking on send")

  // wait 5 seconds before reading it:
  time.Sleep(5000*time.Millisecond)
  num := <- iChan
  fmt.Printf("this probably come BEFORE the function prints")

Mutex's

GO has a simple general-purpose mutex. Locking blocks if it's already locked. This (using a anonymous function), prints "abc" after 2 seconds:

  var waiter sync.Mutex // a simple mutex
  waiter.Lock()
  // calling a function which also uses the waiter mutex:
  go (func() {
    waiter.Lock() // will wait here for main to Unlock
    fmt.Println("abc")
    waiter.Unlock()
  })()

  // let the function run after 2 seconds:
  time.Sleep(time.Second*2)
  waiter.Unlock()

The atomic package (inside of sync) provides a mutex shortcut to do math on a single variable:

atomic.AddInt32(&n, 3) quickly locks n somehow, adds 3, then unlocks. Maybe this is good for very simple stuff? But a mutex seems safer and easier. atomic also provides a LoadInt32 function for weird set-ups where loading a value might be interrupted.

ANY object, dynamic casting

GO uses a hack to get an "any object" type. Everything, even basic types, confirms to the empty interface: interface{}. It's legal to create and pass that around, or make lists of them.

// n has hold any type (and has no legal operations):
var n interface{} = 7
n="cow"

func takeAnyTypeOfInput(n interface{}) { ...

// array of any types (interface{} is the type):
var AA [5]interface{} = [5]interface{}{}

The actual type is decoded in two equally strange ways. There's a funny dynamic cast, and a type-using switch. Ex:

func takesAny(v interface{}) interface{} {
  if v2,success:=v.(int); success { return v2*10 }

  switch v3:=v.(type) {
    case string: return v3+"moo"
    case float64: return v3+1
    default: Printf("%T\n",v3)
  }
  return 0
}
The direct check goes: variable.(typeToConvertTo). What the frack? GO-style, it returns the value if it works, or the default value if it didn't, with an optional true/false whether it worked:
n2,s2:=n.(int) // 7, true
n2:=n.(int) // 7

n3:=n.(float32) // 0.0 (and no error)
n3,s3:=n.(string) // "", false

The type-based switch lists the possible types, as shown.

LinkedList

GO has a linked-list class, but it's painful to use. Without a template system, it only holds generic objects (interface{}). It doesn't work with the special foreach. You also need to call a set-up function since GO has no constructors:

L := list.New()
L.PushBack("cow")
L.PushBack("bear")
e5:=L.PushFront(5)
L.InsertAfter(6,e5)

for e:=L.Front(); e!=nil; e=e.Next() { Println(e.Value) }
// (the foreach style won't work -- no range on lists)
// 5 6 cow bear
Notice how New() isn't new: new(list) won't work, since the bare struct would need to be initialized. New() is merely a static creation function using a similar name.