by alexander white ©
- 0 - installation on a Mac
- 1 - packages
- 2 - remote packages
- 3 - variable syntax
- 4 - functions syntax
- 5 - loops syntax
- 6 - if/else syntax
- 7 - switch syntax
- 8 - structs
- 9 - moar structs
- 10 - methods
- 11 - interfaces
- 12 - closure
- 13 - arrays
- 14 - slices
- 15 - variadic functions
Youtube: link to playlist
If it isn't already on your system, Install homebrew. Occasionally, OS X updates may break homebrew, in which case unistall and reinstall it. From the Terminal, install golang and mercurial.
brew install hg go
Setup the GOPATH. We also add a couple of paths to PATH for convenience.
mkdir $HOME/go
export GOPATH=$HOME/go
export PATH=$PATH:$GOPATH/bin
export PATH=$PATH:/usr/local/opt/go/libexec/bin
Now let's make a Hello World application. Make a new directory, cd into that directory, and create a new .go file.
mkdir -p $GOPATH/src/github.com/hello
cd $GOPATH/src/github.com/hello
touch hello.go
With your favorite text editor, open hello.go, copy in the following code, and save the file.
package main
import "fmt"
func main() {
fmt.Printf("Hello, world.\n")
}
Run your go file
go run hello.go
Alternatively, you can compile the .go file into a binary, and execute the binary.
go install
hello
Go application can be organized into multiple files by using packages. Go has a rich standard library of packages built in. To import a package use the import keyword. The following line imports the fmt package.
import "fmt"
You can have as many lines of import statements as you wish, but there is also a shorthand for importing multiple packages.
import(
"fmt"
"math/rand"
)
By conventions, the name of the package object that is imported is the last component of the path of the the package name. For import "fmt" that will simply be fmt. For import "math/rand" it will be rand. For example,
fmt.Println("Hello World")
rand.Seed(1413241234)
var randomNumer int = rand.Intn(15)
You can create your own packages. Create the file ~/go/src/life/life.go. Copy in the following code using your favorite text editor.
package life
var Life=42
func Halflife() int{
return Life/2
}
Capitalized variables and functions that are declared in a package will be "public", and can be called via dot notation. Lower case variables and functions are private. You could use the life package in demo.go as follows.
package main
import(
"fmt"
"life"
)
func main(){
fmt.Println("Life = ", life.Life, " and halflife = ", life.Halflife())
}
This would yield.
go run demo.go
Life = 42 and halflife = 21
Go makes it easy to copy remote repositories into your Go project using the command line tool go get. Let's copy the go-martini repo into our project.
go get github.com/go-martini/martini
This will add the go-martini into the ~/go/pkg file. Now we should be able to import go-martini into one of our own Go programs.
import "github.com/go-martini/martini"
Martini can be used to make a webserver. A Go program that runs a simple webserver on you local host might look like,
package main
import "github.com/go-martini/martini"
func main() {
m := martini.Classic()
m.Get("/", func() string {
return "Hello world!"
})
m.Run()
}
Save this code to the file ~/go/src/server/server.go. Run your server with the command,
go run server.go
If everything went as planned, you should have a server running on http://localhost:3000
To declare a variable use the format var [variable-name] [type]. Notice that the type comes last. For example,
var myInteger int
You can also initialize a variable right in the declaration.
var myInteger int = 10
You can declare and initialize multiple variables at once.
var myInt, yourInt int = 10, 20
You can declare a LOT of variables at once.
var(
x, y, z int = 1, 2, 3
hiThere string = "hello world"
befuddling complex64 = 3.14 + 6.28i
)
Within the scope of a function, there is a shorthand for declaring and initialize variables using the := operator. The following two lines are identical.
//these two lines are identical
var x int = 99
x := 99
These are all the variable types supported in go...
bool
string
int int8 int16 int32 int64 uint uint8 uint16 uint32 uint64 uintptr
byte // alias for uint8
rune // alias for int32, represents a Unicode code point
float32 float64
You can also declare a constant.
const forever string = "long time" //a type constant
const tomorrow = "short time" // an untyped constant
You can read more about typed and untyped constants on the official Go blog
Function syntax in Go is C-like, but with some big differences. For one, when declaring a function the type comes last (similar to variable). The format is func [function-name]([parameters]) [return-type]
.
func halfer(whole int) int{
return whole/2
}
When declaring multiple parameters of the same type, only the last variable needs to have the type specified.
func halfer(onePart,anotherPart int)int{
return (onePart+anotherPart)/2
}
A big change from C is that functions can return more than one variable.
func sixtyFourty(whole int) (int,int) {
sixty := whole *6/10
fourty := whole-sixty
return sixty, fourty
}
func main(){
var onePart, anotherPart int
onePart, anotherPart = sixtyFourty(99)
}
We can also give the return values names in the fucntion declaration. Rewriting the sixtyFourty() function,
func sixtyFourty(whole int) (sixty, fourty int) {
sixty = whole *6/10
fourty = whole-sixty
return // we don't have to specify the return variable because we have already defined them in the func declaration
}
for
loops are C-like, except you do not put ()s around the parameters. You can also use the := operator to declare the iterator variables. The {}s are mandatory.
for i:=0; i<10; i++{
fmt.Println(i)
}
You can exclude some or all of the parameters.
counter := 10
for ; counter>0; {
fmt.Println(counter)
counter --
}
At this point, our for
loop functions identically to a while loop. In fact, there are no while or do-while loops in Go at all. The syntax the for/while loops can be simplified further by ommitting the ;s.
counter := 10
for counter>0 { //a while loop in go
fmt.Println(counter)
counter --
}
If we leave out all the parameters, a for loop will become infinite. The break
keyword can be used to exite any loop.
for { //loop forever...
if isDone(){
break //unless you break out of the loop
}
}
You can also loop over an array, slice, map or string in Go using the range keyword.
mySlice := []string{"a","b","y","z"}
for index, element := range mySlice {
fmt.Println(index, element) //will print "0 a", etc...
}
If you don't care about the index, you can use _ instead of a variable. _ indicates we expect a variable to be returned, but we don't care about it's value. On the other hand, if we dont' care about the element, we simply exclude , element
.
iq := map[string]int{"UT": 115, "OU": 90, "A&M": 75}
for index := range iq {
fmt.Println(index, element) //will print "UT", etc...
}
for _, element := range iq {
fmt.Println( element) //will print "115", etc...
}
For if
statements in Go you do not place ()s around the condition. However, you must place {}s around the body. Mandatory!
random := rand.Intn(10)
if random<5 {
fmt.Println("our number is less than 5")
}
An else
statment must be on the same line as the preceding }
.
random := rand.Intn(10)
if random<5 {
fmt.Println("our number is < 5")
} else {
fmt.Println("our number is >= 5")
}
The form of theelse if
follows accordingly.
random := rand.Intn(10)
if random<3 {
fmt.Println("our number is < 3")
} else if random<7 {
fmt.Println("our number is <7")
} else {
fmt.Println("our number is >= 7")
}
Similar to a for
statements, we can also declare and intialize a variable right in the if
statement. The scope of that variable will be limited to if/else statement.
if random := rand.Intn(10); random<3 {
fmt.Println("our number is < 3")
} else if random<7 {
fmt.Println("our number is <7")
} else {
fmt.Println("our number is >= 7")
}
Similar to if
statments in Go, the variable being evaluated by an if statement is not place within ()s. Also, Go switch
statements do not require the break
keyword to avoid falling through into the next case. This is great as it eliminates a whole class of errors that have caused switch
statements to be shunned.
os := "OS X"
switch os{
case "OS X":
fmt.Println("MAC.")
case "linux":
fmt.Println("Linus T's system.")
default:
fmt.Printf("%s.", os)
}
If for some reason you do want one case to fallthrough into the next you can use the fallthrough
keyword.
os := "OS X"
switch os{
case "OS X":
fmt.Println("MAC.")
fallthrough
default:
fmt.Printf("Say hi every time!")
}
Again, like the if
statement, you can declare and initialize a variable right in switch
line. The variable will be limited to the scope of the switch
statement.
switch os := "OS X"; os {
case "OS X":
fmt.Println("MAC.")
case "linux":
fmt.Println("Linus T's system.")
default:
fmt.Printf("%s.", os)
}
Lastly, you do not have to give a switch
statement a variable to evaluate at all. In this circumstance, conditions will be evaluated for each case
, essentiall making this form of a switch
statement an alternative to an if/else statment.
os := "OS X"
switch {
case os=="OS X":
fmt.Println("MAC.")
case os=="linux":
fmt.Println("Linus T's system.")
default:
fmt.Printf("%s.", os)
}
Basic struct
syntax is straight forward.
type vertex struct{
int x
int y
int z
}
We can also use some of the same shorthands that we use for declaring variable when we declare fields.
type vertex struct{
int x, y, z
}
We can use dot notation to set the values of the fields. Additionally, we can use {} to instatiate a struct upon declaration.
func main(){
var vertA vertex
vertA.x = 2
vertA.y, vertA.z = 3, 5
vertB := vertex{4,6,10}
/// do something with vertA and vertB .....
}
If we are importing a package, any struct given a type name that starts with a capital letter will be exported (available to be used). Similarly, only those fields of the struct that start with capital letter will be expoerted.
////in vertex.go
type Vertex struct{
int X
int Y
int Z
int t //this field would not be exported
}
Use dot notation with the name of the package to refer to the imported type.
////in main.go
package main
imports "vertex"
func main(){
var vert vertex.Vertex
vert.X = 12
vert.t = 99 //THIS WOULD YIELD AN EROR
/// do something with vert .....
}
In additiona to the {} shorthand for declaring and initializing structs I mention above, you can also initialize a struct by specifically referencing a field. By initializing structs this way, un-initiailzed structs will be initialized with their default value (0.0 for a float64).
type vertex struct{
x, y float64
}
func main(){
vertA := vertex{3.0, 4.0} // is equivalent to...
vertB := vertex{x:3.0, y:4.0}
vertC:= vertex{x:3.0} // y field would be 0.0, whereas....
vertD:= vertex{3.0} // would cause an error
}
Go is not (really) an object oriented language, but structs can embed other struct types, which bears some resemblance to inherittance in object oriented languages. (Methods
and Interfaces
make structs even more OO-like.)
type vertex struct{
x, y float64
}
type vertex3D struct{
vertex
z float64
}
To set the value of the fields of an embedded type, you use dot notation. Specifically stating the type of the embedded struct is optional.
var vert3D vertex3D
//these both work
vert3D.vertex.x = 2.0
vert3D.y = 4.0
You can also use :=
operator shorthand to declare and initialize a struct that contains an embedded struct.
vert := vertex{1.0,2.0}
// all the fields of these 3 vertex3D variable will have the same values
vert3D_A := vertex3D{vert, 3.0}
vert3D_B := vertex3D{vertex:vert,z: 3.0
vert3D_C := vertex3D{vertex:vertex{1.0, 2.0}, z:3.0}
Another feature of Go structs that make them object-oriented-like is methods. In Go you can attach methods to structs. Method declaratin syntax is similar to function syntax, with the key difference being that after the func keyword you must indicate what struct type the method is being attached to (in this case, myStruct
).
type myStruct struct{
intField int
}
func (ms myStruct) myMethodName(param1 int, param2 int) int{
return ms.intField + param1 + param2
}
Methods are called on a struct using dot notation.
func main(){
sumert := myStruct{1} // sumert is a variable of type myStruct, which we initialize here
var retVal int
retVal = sumert.myMethodName(3,4) // retVal will equal 8
}
Method can also act on structs in a "by value" or "by reference" manner. That is to say, a by reference method will have the ability to make changes to the fields of struct even after the method has returned.
func (ms myStruct) addByValue(x int) { ///it is OK to leave the return type off a function or method if we are not returning a value
ms.intField+=x
fmt.Println("ByValue internal value ", ms.intField)
}
func (ms *myStruct) addByReference(x int) {
ms.intField+=x
fmt.Println("ByReference internal value ", ms.intField)
}
func main(){
myVar := 1
myVar.addByValue(3)
fmt.Println("main func value ", myVar)
myVar.addByReference(3)
fmt.Println("main func value ", myVar)
}
This code will yield the following output. Note that changes made to field within a "by value" method will not persist. "By reference" changes will.
ByValue internal value 4
main func value 1
ByReference internal value 4
main func value 4
Go is smart enough to tell what type of method is being called without the coder specifically having to caste the struct variable (or even pointer to a struct variable) as either a struct or pointer to a struct. Go will automatically sort out if a variable needs to be dereference or not without throwing a compile error.
func main(){
myVar := myStruct{1}
myPtr := &myStruct{2} // a pointer to a struct
//all these methods calls will work
myVar.incByReference(3)
(&myVar).incByReference(3)
myPtr.incByValue(3)
}
Go isn't a traditionally Object Oriented language, and thus lacks the feature of inheritance. Specifically, a struct (the "child") that embeds another struct (the "parent") cannot be set to or passed as a variable of the type parent. For example,
struct car type{
cost float
}
func (c car) canStart() bool{
return true
}
struct delorian type{
car //embed the car struct
coolnessFactor int
}
func main(){
var myCar delorian
myCar.cost = 10000.0
bool starts = myCar.canStart() //this works, variable of type kid can access parent fields and methods
var genericCar car
genericCar = myCar //ERROR! this does not work, embedding a type does not yield OO inheritance
}
Instead, Go provide for a feuture call interfaces
. An interface is a type. An interface is a type. Interface... type. An interface is a type that declares itself as being able to handles a set of methods. Interfaces do not pertain to fields at all (unlike OO objects which define a set of methods AND properties). So if we were to refactor the above code for interfaces, we would get...
struct car interface{
canStart() bool{
}
struct delorian type{
coolnessFactor int
}
func (d delorian) canStart() bool{
return false
}
func main(){
var myCar delorian
myCar.cost = 10000.0
bool starts = myCar.canStart() //this works, variable of type kid can access parent fields and methods
var genericCar car
genericCar = myCar //this works. delorian impliments the method canStart so it satisfies the conditions required to be type car
}
Without interfaces the type system in Go would be extremely constrictive.
There is also closure in go! In Go, functions are first class which means we can have function types, pass fuctions as parameters and return functions to and from functions and methods, and have closure. Lets define a counter function that returns a function. The function that it returns will inciment a count variable, theCount
. theCount
will take no parameters and return no value itself.
func Mycounter() func(){
theCount:= 0
increment := func(){
theCount++
fmt.Println("The count is", theCount)
}
return increment
}
It's important to note that the scope of the increment
function is the scope of the function that it is decalred within. That is to say, the variabel theCount
is within the scope of the function increment
. That means that even after Mycounter() returns, theCount
variable will resist in increment
's closure. We can use this as follows.
func main(){
var incFunc func()
incFunc = MyCounter()
for i:=0; i<5; i++{
incFunc() //use () to execute increment
}
}
This will give the following output.
The count is 1
The count is 2
The count is 3
The count is 4
The count is 5
Of course in Go we can also return multiple value, including multiple func variables. Let's impliment a getter funtion for our count variable. This function will take no parameters and return an int, thus it's type will be func() int
. Note that this is a completely different type than our increment func variable, which had a type of func()
. (Let's also add a paramater to Mycounter so we can set our counters initial value.)
func Mycounter(initialCount int) ( func(), func() int){
theCount:= initialCount
increment := func(){
theCount++
fmt.Println("The count is", theCount)
}
get := func() int{
fmt.Println("The count is", theCount)
return theCount
}
return increment, get
}
This could be used as follows...
func main(){
incFunc, getFunc := Mycounter(10)
for i:=0; i<5; i++{
incFunc()
}
fmt.Println("The final value is ", getFunc())
}
... yielding this output.
The count is 11
The count is 12
The count is 13
The count is 14
The count is 15
The count is 15
The final value is 15
Notice that incriment and get functions share the same theCount value. It persists encapsulated in Closureland!
Lastly, the declaration for our Mycounter function is starting to get a little long and ugly. Of course, it could get even worse too. Let imagine a method declaration,
func (m myStruct) uglyMethod(paramA, paramB, paramC int) ( func(pA,pB) float, func(p1, p2, p3) int){
....
}
That's getting a little dense. To clarify thing we could could declare some new function types.
type firstFuncType func(pA,pB) float
type secondFuncType func(p1, p2, p3) int
func (m myStruct) uglyMethod(paramA int, paramB, paramC) ( firstFuncType, secondFuncType ){
....
}
That is a little more readable, in particular if we end up reusing those new types multiple times in our code.
Array declaration syntax is straight forward. We have to decalre both the value of the elements of the array AND the length. All the declaration below declare arrays of type [5]int. Note, for arrC, Go will infer the length of the array if we use [...]
during assignment.
var arrEmpty [5]int
var arrA = [5]int {1,2,3,4,5}
arrB := [5]int {3,4,5,6,7}
arrC := [...]int {4,5,6,7,8}
I repeat, the type of an array consists of both it's length And it's element type. This won't work....
arrA := [5]int {1,2,3,4,5}
arrB := [4]int {4,5,6,7}
arrA=arrB //// ERROR! arrA and arrB variables of different types, specifically [5]int and [4]int respectively
The other crucial thing about Go arrays is that an array is a value, not a pointer. In C, an array is a pointer to block of memory. Not so in Go. If you set a Go array arrA
equal to another Go array arrB
, the two arrays will represent values in two completely differnt blocks of memory. Any subsequent changes to arrA will have no effect on arrB.
func main(){
var arrA [5]int
var arrB = [5]int {3,4,5,6,7}
arrA=arrB //the values of arrB are copied into the memory of arrA
arrA[2]=15 //arrB will be uneffected
fmt.Println(arrA,arrB)
fmt.Printf("%p \n%p\n",&arrA,&arrB) //%p is formatting for a point, dereference arrA with & to get a pointer
}
This yields the following output. Notice changes to one array do not effect the other and the arrays occupy different locations in memory.
[3 4 15 6 7] [3 4 5 6 7]
0x2081ba150
0x2081ba180
90%+ of the time the you work with lists of values in Go, you will be working with slices, not arrays. Slices are an abstraction built on top of arrays. The type of a slice indicates the type of the elements. A slice can contain any number of elements, unlike an array.
var mySlice []int // a slice of ints, length is variable
var myArray [5]int // a array of ints that is 5 elements long
There are the expected ways to declare a slice. Declaration is very similar to array decalartion.
var aSlice []int //nil slice, len and cap are zero
var bSlice = [int]{1,2,3}
cSlice := [int]{3,4,5}
You can make a slice from an array (or another slice). That is, to take a slice from it. The format is arrayName[startIndexInclusive:endIndexExclusive]. You can leave off one or both of the start or end indices.
myArray := [6]int{1,2,3,4,5,6}
sliceA := myArray[2:5] // value will be {3,4,5}
sliceB := myArray[:3] // value will be {0,1,2}
silceC := myArray[3:] // claue will be {2,3,4,5,6}
sliceD := myArray[:] // value will be {1,2,3,4,5,6}
A slice is actually comprised of 3 parts under the hood: a pointer to an element in an array, a length and a capacity. The length is the number of elements in the slice. The capacity is the total number of elements the slice can have before go needs to reallocate memory to hold the slice. For example, take the following main function.
func main(){
arrA := [6]int{1,2,3,4,5,6}
slcA := arrA[0:3]
fmt.Println( slcA, len(slcA), cap(slcA))
fmt.Printf("%p\n%p", &arrA, &slcA[0])
}
The "%p"
formatting allows us to print out the values of pointers. &
gets of the pointer for a value. In this case we get the pointers to the start of the array and the first element of the slice. This will generate the output...
[1 2 3] 3 6
0x2081ba150
0x2081ba150
The slice that we made has a length of 3 and a capacity of 6 (the size of the underlying array). The pointer values for the start of the array and first element of the slice are identical (for now). Currently, Go is using our array as the underlying storage for our slice.
We can append elements to the end of a slice using the append function. Lets append a bunch of elements in a for loop.
func main(){
arrA := [6]int{1,2,3,4,5,6}
slcA := arrA[0:3]
fmt.Println( "\n",arrA,"\n",slcA,"\n") //print out the value of both our array and slice
for i:=0;i<10;i++{
slcA = append (slcA,90+i) //append an int to the end of our []int slice
//print out the address of the first element of the slice, the length, the capacity and the value of the slice
fmt.Printf("P:%p L:%2v C:%2v S:%v\n", &slcA[0],len(slcA), cap(slcA), slcA,)
}
fmt.Println( "\n",arrA,"\n",slcA) //print out the final value of our array and slice
}
Will render output...
[1 2 3 4 5 6]
[1 2 3]
P:0x2081ba150 L: 3 C: 6 S:[1 2 3]
P:0x2081ba150 L: 4 C: 6 S:[1 2 3 90]
P:0x2081ba150 L: 5 C: 6 S:[1 2 3 90 91]
P:0x2081ba150 L: 6 C: 6 S:[1 2 3 90 91 92]
P:0x2081c8000 L: 7 C:12 S:[1 2 3 90 91 92 93]
P:0x2081c8000 L: 8 C:12 S:[1 2 3 90 91 92 93 94]
P:0x2081c8000 L: 9 C:12 S:[1 2 3 90 91 92 93 94 95]
P:0x2081c8000 L:10 C:12 S:[1 2 3 90 91 92 93 94 95 96]
P:0x2081c8000 L:11 C:12 S:[1 2 3 90 91 92 93 94 95 96 97]
P:0x2081c8000 L:12 C:12 S:[1 2 3 90 91 92 93 94 95 96 97 98]
P:0x2081cd250 L:13 C:24 S:[1 2 3 90 91 92 93 94 95 96 97 98 99]
[1 2 3 90 91 92]
[1 2 3 90 91 92 93 94 95 96 97 98 99]
Notice a few things.
- The original capacity of our slice matches the original size of our array.
- The location in memory of the values of our slice stays the same until the slice's length exceeds the slice's capacity. At this point, under the hood, Go allocates a new array acciated with a larger contiguous block of memory. The slice's capacity is doubled. Subsequent append calls won't require new memory allocation until the capacity is again exceed (the last append call also exceeds capacity)
- The first 3 append calls overwrote the final 3 elements of our original array variable! So be careful when working with both arrays and slices. You may be better off just working with slices.
Lastly,consider this. The call slcA[0]=-5
will a have different effect depending of where it is made. If we call it before the for loop, when our array and our slice are sharing memory, our final values will be...
[-5 2 3 90 91 92]
[-5 2 3 90 91 92 93 94 95 96 97 98 99]
If we call it after the loop, when slice is using a different block of memory, the final values will be...
[1 2 3 90 91 92]
[-5 2 3 90 91 92 93 94 95 96 97 98 99]
A variadic function in Go is a function that can take a variable number of parameter. An example of a variadic function is fmt.Println()
. The variardic parameter is declared with the format parameterName ...elementType. parameterName would then be available as a slice of type []elementType within the body of the function. For example,
func vardiacFun( params ...int){
fmt.Printf("Parameter type: %T\n",params)
fmt.Println(params)
for _,elem :=range params{ //loop of the parameter
fmt.Println(elem)
}
}
func main() {
vardiacFun(1,3,4,5,6)
}
This would yield the following output.
Parameter type: []int
[1 2 3 4 5]
1
2
3
4
5
A variadic function can have more than just the variadic parameter, but the variadic parameter must be the last parameter.
func vardiacFun( s1, s2 string, params ...int){ //variadic parameter must come last
//do something here
}
If it is your preference, you can always avoid using a variadic function by using a normal slice as a parameter. We can rewrite our first example in a nonvariadic style as below. The bodies of variadic()
and nonvariadic()
are identical. The differences are in the functions' declarations and how they are called in main()
.
func nonVardiacFun( params []int){
fmt.Printf("%T\n",params)
fmt.Println(params)
for _,elem :=range params{
fmt.Println(elem)
}
}
func main() {
nonVardiacFun([]int{1,2,3,4,5})
}
- if-else statements
- switch statements
- structs
- arrays
- slices
- maps
- defers
- methods
- interfaces
- errors
- closure
- variadic functions
- go routines
- channels
- select
- json/xml
- go command line tools
- "go translated"... take confusing go syntax, and explain it
- MOAR!, make a suggestion below
###sources
- Go blog
- tour of Go
- Go by example
- golang docs
- golang weekls
- closure and first class function code walk
© All rights reserved by Alexander White.