Golang Crash Course from a Pythonista
File Structure
Executable programs must have a main
function, and the package name must be defined as follows:
package main func main() { // Do something here. }
Imports
Packages can be imported using their relative paths like this:
package main import ( "fmt" "math/rand" ) func main() { fmt.Println("My favorite number is", rand.Intn(10)) }
Visibility
Items within packages that start with capital letters are exported and can be accessed by other packages. This will raise an error because pi
is not exported:
package main import ( "fmt" "math" ) func main() { fmt.Println(math.pi) }
Functions
In Golang, functions are defined using the func
keyword instead of def
. Parameters and return types must be explicitly defined, like this:
package main import "fmt" func add(x int, y int) int { return x + y } func main() { fmt.Println(add(42, 13)) }
You can also simplify parameter type declarations by only specifying the type for the last parameter. The preceding parameters will be assumed to be of the same type. In this example, both x
and y
are integers:
func add(x, y int) int { return x + y }
Named Return Values
In Golang, return values can be named. In this example, the split
function returns two named values, x
and y
. These names can help document the purpose of the return values:
package main import "fmt" func split(sum int) (x, y int) { x = sum * 4 / 9 y = sum - x return } func main() { fmt.Println(split(17)) }
If you do not name your return values, the function will use a "naked" return. Avoid naked returns as much as possible, especially in larger functions, as they make code harder to read and understand.
Variables
the var
statement is used for defining variables. Like arguments in functions you can define multiple variable types by giving type to last one. In this example, foo, bar and baz is booleans.
var foo, bar, baz bool
If variables are not initialized, their defaults are:
- 0 for numeric types,
- false for the boolean type, and
- "" (the empty string) for strings.
Variable Scopes
Variables scoped by packages and functions. and they can be initialized like this:
var foo, bar, baz bool = true, true, false
When you're creating variables with initializers, you don't need to explain their types. This will give the same result:
var foo, bar, baz = true, true, false
If you're using variables with initializers, defining a variable can be shortened like this:
foo, bar, baz := true, true, false
When you use :=, you don't need to use var statement.
Types
Here is the list of types in Golang:
- 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 -complex64
- complex128
Note: The int, uint, and uintptr types are usually 32 bits wide on 32-bit systems and 64 bits wide on 64-bit systems. When you need an integer value you should use int unless you have a specific reason to use a sized or unsigned integer type.
Type Castings
The expression NewType(OldTypeVariable) converts the value OldTypeVariable to the NewType.
var i int = 42 var f float64 = float64(i) var u uint = uint(f)
Constants
Constants are same with variables but they are not mutable. They can be string, number or boolean values. const keyword used to define constant instead of var.
const pi float = 3.14
Loops
Other languages has while and for loops but Golang has only for loop. But it can be used to construct while loop and infinitive loops too.
Most basic usage is like the javascript, in same line you initialize the variable, set the condition to continue loop and an executor to change your variable. Only difference is you don't put them inside a pharantesis.
package main import "fmt" func main() { sum := 0 for i := 0; i < 10; i++ { sum += i } fmt.Println(sum) }
By the way you dont need to initialize your variable in for loop, you can use pre initialized variable.
package main import "fmt" func main() { sum := 1 for ; sum < 1000; { sum += sum } fmt.Println(sum) }
And you dont need to type your executor to the for loop. This usage is basically while loop in other languages:
package main import "fmt" func main() { sum := 1 for sum < 1000 { sum += sum } fmt.Println(sum) }
And... you can create infinite loop by using for loop without any parameters like this:
package main func main() { for { } }
Control Flows
If Statements
If statements are like for loops but you only create a condition
package main import ( "fmt" "math" ) func sqrt(x float64) string { if x < 0 { return sqrt(-x) + "i" } return fmt.Sprint(math.Sqrt(x)) } func main() { fmt.Println(sqrt(2), sqrt(-4)) }
Like walrus operator in Python, you can define variable and check that in same line in golang:
func pow(x, n, lim float64) float64 { if v := math.Pow(x, n); v < lim { return v } return lim }
Switch statements
Switch statements are used to create sequence of if else conditions like other languages. Switch statements are evaluated from top to bottom.
package main import ( "fmt" "runtime" ) func main() { fmt.Print("Go runs on ") switch os := runtime.GOOS; os { case "darwin": fmt.Println("OS X.") case "linux": fmt.Println("Linux.") default: // freebsd, openbsd, // plan9, windows... fmt.Printf("%s.\n", os) } }
Also you can use predefined variables to create switch blocks:
func main() { t := time.Now() switch { case t.Hour() < 12: fmt.Println("Good morning!") case t.Hour() < 17: fmt.Println("Good afternoon.") default: fmt.Println("Good evening.") } }
Defers
I didn't see this concept before. Defers are functions to call when a caller function is finished. When a function is finished, it's deferred calls are executed last in first out order:
package main import "fmt" func main() { fmt.Println("counting") for i := 0; i < 10; i++ { defer fmt.Println(i) } fmt.Println("done") }
This code will print 0 to 9 after main function is finished
Pointers
Go supports pointers, which store the memory address of a value.
The
&
operator is used to obtain the pointer to a variable.i := 42 p = &i
The
*
operator is used to access or update the value the pointer refers to.fmt.Println(*p) // reads the value of i through the pointer p *p = 21 // updates the value of i through the pointer p
This process is called "dereferencing" or "indirecting."
Structs
A struct is a collection of fields.
package main import "fmt" type Vector struct { X int Y int } func main() { v := Vector{1, 2} v.X = 4 fmt.Println(v.X) }
They are initialized with {} and their values are accessed by dot.
Pointers to Structs
When you're accessing struct instances via pointers, you can access it's fields without using *
package main import "fmt" type Vertex struct { X int Y int } func main() { v := Vertex{1, 2} p := &v // (*p).X = 1e9 < -- You can do it like that but there's a shortcut: p.X = 1e9 // This is the shortcut. fmt.Println(v) }
Arrays and Slices
In Golang there are two kind of data types which is used contain multiple values of a type: arrays and slices. Arrays are primitive, they contain static number of elements but they are slightly faster. Slices are most commonly used, they can be resized, changed etc.
Arrays:
- Perform slightly better when their size is fixed.
- Minimal runtime overhead.
- Commonly used for low-level and fixed-size data structures.
Slices:
- More flexible and practical.
- Adds a minimal abstraction layer over arrays, which introduces slight runtime overhead.
- Preferred in most cases, as they better align with Go's standard data structures.
If the size is fixed and doesn't change often, consider using an array. However, if a dynamic size or flexibility is required, slices are the better choice.
Arrays
In golang, arrays can not be resized. O_o They are defined as [number of elements]Type. For example var a [10]int
creates an array called a with 10 integers.
Slices
In golang slices are used create sliced views for arrays. They are defined as []Type = Array[low: high]
. For example: var s []int = a[1:4]
creates a s variable which is slice from the a array by getting first and fourth elements.
Note that slices are not copies, they are references. The referenced array is changed, slice is changed too. Here is an example that creates names array and creates two slices from it. When you check the output you can see that when referenced array (names) are changed, a and b slices are changed too:
package main import "fmt" func main() { names := [4]string{ "John", "Paul", "George", "Ringo", } fmt.Println(names) a := names[0:2] b := names[1:3] fmt.Println(a, b) b[0] = "XXX" fmt.Println(a, b) fmt.Println(names) }
Slice Literals
Slice literals are shorcut for defining an array and getting a slice from it IMO.
This is an array literal:
[3]bool{true, true, false}
And this creates the same array as above, then builds a slice that references it:
[]bool{true, true, false}
Here is a bigger example. In this case we're creating 3 slices q, r and s without defining their arrays. Also you can see that we can use structs to define array types.
package main import "fmt" func main() { q := []int{2, 3, 5, 7, 11, 13} fmt.Println(q) r := []bool{true, false, true, true, false, true} fmt.Println(r) s := []struct { i int b bool }{ {2, true}, {3, false}, {5, true}, {7, true}, {11, false}, {13, true}, } fmt.Println(s) }
Slices are dynamically sized, so this usage is actually to create array like structures which are dynamically sized. You can add or remove items from slices instead of arrays.
When slice low and high points are not described, lowest and highest available points will be used. For example if a is an array with 10 elements, a[:10] means a[0:10], or a[3:] means a[:10].
Slice Length and Capacity
A slice has two key properties: length and capacity.
- Length: The number of elements the slice currently holds.
- Capacity: The total number of elements available in the underlying array, starting from the first element of the slice.
You can retrieve the length and capacity of a slice s
using the expressions len(s)
and cap(s)
.
Slices can be extended by re-slicing them, as long as they don’t exceed their capacity.
Try modifying one of the slice operations in the example program to extend the slice beyond its capacity and observe the result.
package main import "fmt" func main() { s := []int{2, 3, 5, 7, 11, 13} printSlice(s) // Slice the slice to give it zero length. s = s[:0] printSlice(s) // Extend its length. s = s[:4] printSlice(s) // Drop its first two values. s = s[2:] printSlice(s) } func printSlice(s []int) { fmt.Printf("len=%d cap=%d %v\n", len(s), cap(s), s) }
Making Slices
There's a command called make, which is used to create an empty slice with given size.
a := make([]int, 5)
You can also define it's capacity (maximum size). In this example we have a slice with 5 zeros and it\'s maximum capacity is 10.
a := make([]int, 5, 10)
Slices inside slices
Slices can contain other slices. Here's an example of tic tac toe game board. It\'s simply a 2D array.
package main import ( "fmt" "strings" ) func main() { // Create a tic-tac-toe board. board := [][]string{ // [][] --> Slice contains slices. []string{"_", "_", "_"}, []string{"_", "_", "_"}, []string{"_", "_", "_"}, } // The players take turns. board[0][0] = "X" board[2][2] = "O" board[1][2] = "X" board[1][0] = "O" board[0][2] = "X" for i := 0; i < len(board); i++ { fmt.Printf("%s\n", strings.Join(board[i], " ")) } }
This is equavelent for this in Python:
board = [ ['_', '_', '_'], ['_', '_', '_'], ['_', '_', '_'] ] board[0][0] = "X" board[2][2] = "O" board[1][2] = "X" board[1][0] = "O" board[0][2] = "X" for row in board: print(' '.join(row))
Actually this is the long way to define board in Python you can just do this:
board = [['_'] * 3] * 3
Appending
In Golang appending to a slice is done by append
function. I'm not sure why is this not a method of a slice but probably lack of methods causing this. Append function takes slice as first parameter rest of parameters are things to add to that slice.
var s []int printSlice(s) s = append(s, 1) printSlice(s)
This is equavelent to this code block in Python:
s = [] print(s) s.append(1) print(s)
Range
Range is enumuerate in Python. Used to run over loops for every element and their indexes.
package main import "fmt" var pow = []int{1, 2, 4, 8, 16, 32, 64, 128} func main() { for i, v := range pow { fmt.Printf("2**%d = %d\n", i, v) } }
This is equavelent to this code block in Python
pow = [1, 2, 4, 8, 16, 32, 64, 128] for i, v in enumerate(pow): print(2 ** i, '=', v)
Using index and value together is not madatory, you can by pass one of them. For example by passing the index:
var lst = []str{'A', 'B', 'C'} for _, value := range lst { // Prints values fmt.Printf(value) } for i, _ := range lst { // Prints indexes fmt.Printf(i) }
This is equavelent to this code block in Python:
lst = ['A', 'B', 'C'] for _, value in enumerate(lst): print(value) for i, _ in enumerate(lst): print(value)