SHODO
This presentation is in English. Many terms do not have a proper translation in French.
If you have any question at any time, feel free to ask.
Morning session
Afternoon session
A kata is a simple programming exercise, useful to practice and learn new things.
FizzBuzz is a kata with simple rules: a function that, for a given number, will return:
Three knights of programming were looking for a language that meets the scale of the company.
Robert Griesemer, Rob Pike and Ken Thompson had three main considerations:
Software developer.
Worked at Google, on compiler, and JavaScript v8 engine.
Member of the Unix system team at Bell Labs.
Work at Google since 2002.
Co-author of "The Practice of Programming".
Co-creator of UTF-8.
Member of the Unix system team at Bell Labs.
Work at Google since 2006.
Designer of the Unix System.
Inventor of B language.
Design began as a 20% project at Google
Language is open-sourced
First stable version, Go 1.0, released
Go 1.24, latest version, released
Go 1.18 released, with generics
Go 2.0?
The next major release
The Golang mascot!
Designed by Renee French since the birth of the project.
Go is the official name of the language.
To make things easier while searching for information about the language, registering a domain name, etc., Golang name was created.
If you're searching on Google, then Golang should preferably used for now.
| Pros | Cons |
|---|---|
| Simplicity | Too simplistic |
| Strong typing | Lack of maturity |
| Fast compile time | Not really OOP |
| Memory efficient | Not suited for large codebase |
| Garbage collection | |
| Concurrency | |
| Tooling & community |
Want to know more on Go usage?
Check this survey: https://go.dev/blog/survey2021-results
$ go version
go version go1.23.5 darwin/arm64
$ go run -h
usage: go run [build flags] [-exec xprog] package [arguments...]
Run 'go help run' for details.$ git clone https://github.com/nathancastelein/go-course-introduction.git
Cloning into 'go-course-introduction'...
remote: Enumerating objects: 8, done.
remote: Counting objects: 100% (8/8), done.
remote: Compressing objects: 100% (5/5), done.
remote: Total 8 (delta 0), reused 5 (delta 0), pack-reused 0
Receiving objects: 100% (8/8), done.$ cd go-course-introduction
$ go run hello/main.go
Hello there wonderful people!package main
import "fmt"
func main() {
fmt.Println("Hello there wonderful people!")
}Go, the magic wand
Run, build, test, ...
Many options to interact with your code.
go.dev, the resource center
All resources to learn Go and get the best practices
$ go help
Go is a tool for managing Go source code.
Usage:
go <command> [arguments]
The commands are:
bug start a bug report
build compile packages and dependencies
clean remove object files and cached files
doc show documentation for package or symbol
env print Go environment information
fix update packages to use new APIs
fmt gofmt (reformat) package sources
generate generate Go files by processing source
get add dependencies to current module and install them
install compile and install packages and dependencies
list list packages or modules
...Run compiles and runs the named main Go package.
Only work on main package.
$ cd hello
$ go run main.go
Hello there wonderful people!
$ go run .
Hello there wonderful people!
$ cd ../syntax
$ go run .
package github.com/nathancastelein/go-course-introduction/syntax is not a main packageBuild compiles the packages, along with their dependencies, but it does not install the results.
Build an executable if executed in main package.
➜ hello git:(main) ✗ ls
main.go
➜ hello git:(main) ✗ go build
➜ hello git:(main) ✗ ls
hello main.go
➜ hello git:(main) ✗ ./hello
Hello there wonderful people!Install compiles and installs the packages.
Executables are installed in $GOBIN, which defaults to $GOPATH/bin or $HOME/go/bin if the GOPATH is not set.
➜ hello git:(main) ✗ go install
➜ hello git:(main) ✗ which hello
/Users/nathan/go/bin/hello
➜ hello git:(main) ✗ hello
Hello there wonderful people!
➜ hello git:(main) ✗ ls
main.goRun gofmt -l -w.
Gofmt formats Go programs. It uses tabs for indentation and blanks for alignment.
package main
import "fmt"
func main() {
fmt.Println("I'm using a useless semicolon!");
fmt.Println("I'm badly indented!")
fmt.Println("Me too!")
}package main
import "fmt"
func main() {
fmt.Println("I'm using a useless semicolon!")
fmt.Println("I'm badly indented!")
fmt.Println("Me too!")
}package main
import "fmt"
func main() {
fmt.Println("I'm using a useless semicolon!");
fmt.Println("I'm badly indented!")
fmt.Println("Me too!")
}Test automates testing packages.
It recompiles each package along with any files with names matching the file pattern "*_test.go".
➜ fmt ✗ ls *_test.go
errors_test.go export_test.go gostringer_example_test.go state_test.go stringer_test.go example_test.go fmt_test.go scan_test.go stringer_example_test.go
➜ fmt ✗ go test .
ok fmt 0.452s| A Tour of Go | Learn Go in an interactive way | https://go.dev/tour/welcome/1 |
| Go Language Specification | Syntax and specification of Go | https://go.dev/ref/spec |
| Effective Go | Tips for writing clear, idiomatic Go code | https://go.dev/doc/effective_go |
| Go Playground | Write, test and share snippets | https://go.dev/play/ |
Following a survey sent to the community in 2021, here is an overview of the most preferred editors (https://go.dev/blog/survey2021-results):
Main reasons:
Work on your SDEV from your local station with Visual Studio Code!
Not required for today.
package main
import "fmt"
func main() {
fmt.Println("Hello there wonderful people!")
}A package is a collection of source files in the same directory that are compiled together. Functions, types, variables, and constants defined in one source file are visible to all other source files within the same package.
A module is a collection of related Go packages that are released together. A Go repository typically contains only one module, located at the root of the repository.
One module, named github.com/google/uuid, which contains one package, uuid.
Module name is defined into go.mod file.
Create your module (required to start your codebase):
go mod init
Importing an existing module into your module:
go get github.com/google/uuid
package main
import (
"fmt"
"github.com/google/uuid"
)
func main() {
fmt.Println("Hello there wonderful people!")
fmt.Printf("Here is your id: %s\n", uuid.New())
}Using a package function from an already imported module, or a standard module:
module github.com/nathancastelein/go-course-introduction
go 1.20
require github.com/google/uuid v1.5.0
Your go.mod file contains the module name, and the other modules imported by your module.
Package names convention:
| Good | Bad |
|---|---|
| time | bare_metal |
| json | publicCloud |
| strings | utils |
| fmt | common |
The main package is a particular package for Go. It's the entry point to build an application and all of its dependencies.
The main package must have package name main and declare a function main that takes no arguments and returns no value.
package main
func main() {
....
}Go provides a lot of useful libraries as standard:
All of the following pieces of code can be found in syntax folder on the Git repository!
package syntax
import "fmt"
func Boolean() {
var myFirstBoolean bool
mySecondBoolean := true
myThirdBoolean := myFirstBoolean
myFourstBoolean := new(bool)
fmt.Println("My variables are: ", myFirstBoolean, mySecondBoolean, myThirdBoolean, myFourstBoolean)
}In Go, there are multiple ways to declare a new variable.
Declare a new variable
Assign a new value to an existing variable
Variables declared without an explicit initial value are given their Zero value.
The Zero value depends of the type:
func ZeroValue() {
var myBoolean bool
var myInt int
var myString string
fmt.Println("Zero values are: ", myBoolean, myInt, myString)
// print "Zero values are: false 0 "
}A variable that is declared must be used in your code. Otherwise it will not compile.
The ones we will see today
Other types we will not see today
package syntax
import "fmt"
func Boolean() {
itsTrue, itsFalse := true, false
fmt.Println("My booleans are: ", itsTrue, itsFalse)
fmt.Println("Trying some operation: ", itsTrue && itsFalse, itsTrue || itsFalse)
}
True or false?
14 different types, with some aliases.
package syntax
import "fmt"
func Numeric() {
var myInt64 int64
myInt := 10
myFloat64 := 3.14
myInt64 = 20
fmt.Println("Here are numeric types: ", myInt, myInt64, myFloat64)
fmt.Println("With some operation: ", myInt+int(myInt64), float64(myInt)+myFloat64, myInt-int(myFloat64))
}Most common ones: int and float64.
Int is an alias to int32 or int64 depending of the architecture.
package syntax
import "fmt"
func String() {
var myEmptyString string
myString := "Hello"
myStringWithDoubleQuotes := `"Nathan"`
myMultiLineString := `Line 1
Line 2`
fmt.Println("Here are some strings: ", myEmptyString, myString, myStringWithDoubleQuotes, myMultiLineString)
fmt.Println("Size of a string: ", len(myString), len(myEmptyString))
fmt.Println("Operation on a string: ", myString+" "+myStringWithDoubleQuotes)
}Two common packages to create and manipulate strings:
package syntax
import "fmt"
func Fmt() {
myAge := 33
myName := "Nathan"
mySize := 1.75
myInformation := fmt.Sprintf("Hello, I'm %s, I have %d years old, and I am %.2fm tall", myName, myAge, mySize)
fmt.Println(myInformation)
}Powerful package to create strings from other variables.
More information here: https://pkg.go.dev/fmt#hdr-Printing
func If() {
if 2 > 1 {
fmt.Println("Two is superior to one")
} else if 1+1 == 11 {
fmt.Println("1 + 1 equals 11")
} else {
fmt.Println("We're in the else statement")
}
if sum := FuncSumWithVariadicInput(1, 2, 3); sum > 5 {
fmt.Println("The computed sum was superior to 5: ", sum)
}
}func Switch() {
name := "Nathan"
switch name {
case "Nicolas":
fmt.Println("Hello Nicolas!")
case "Nathalie":
fmt.Println("Hello Nathalie!")
case "Nathan":
fmt.Println("Hello Nathan!")
default:
fmt.Println("Who are you? Who who, who who?")
}
myInt := 10
switch {
case myInt > 20:
fmt.Println("What an int!")
case myInt+5 > 15:
fmt.Println("Let's add some compute")
case myInt >= 10:
fmt.Println("My int is not so big: ", myInt)
}
}func For() {
for myVariable := 0; myVariable < 5; myVariable++ {
fmt.Println("I'm in a loop: ", myVariable)
}
for myVariable := range 5 {
fmt.Println("I'm in a loop: ", myVariable)
}
for range 5 { // go 1.22+
fmt.Println("I'm in a loop")
}
myVariable := 0
for myVariable == 0 {
myVariable += 5
}
for {
myVariable++
if myVariable > 10 {
fmt.Println("Stopping the loop, value: ", myVariable)
break
}
}
}For a given number, the function fizzbuzz should return:
Write the algorithm to follow the rules.
file/to/open.go$ go test .File to open:
Test your code:
fizzbuzz/step1/fizzbuzz.gofunc FizzBuzz(input int) string {
fizzBuzzValue := ""
if input%3 == 0 {
fizzBuzzValue += "Fizz"
}
if input%5 == 0 {
fizzBuzzValue += "Buzz"
}
if fizzBuzzValue != "" {
return fizzBuzzValue
}
return fmt.Sprintf("%d", input)
}An array is a numbered sequence of elements of a single type. The number of elements is called the length of the array and is never negative. Arrays a building block for slices.
Slices wrap arrays to give a more general, powerful, and convenient interface to sequences of data. Except for some specific cases, you will always use slices instead of arrays.
package syntax
import "fmt"
func Array() {
var myIntArray [3]int
myIntArray[0] = 3
myIntArray[1] = 2
myIntArray[2] = 1
fmt.Println("Here is an int array", myIntArray)
}Length
Current size of the slice, ie. number of elements contained in the slice.
It may change during the execution.
Length can be discovered with len() function.
Capacity
Size of the underlying array.
Memory currently allocated for the slice. Going over the capacity of a slice implies memory reallocation.
Capacity can be discovered with cap() function.
A slice is defined by three attributes: its type (int, string, ...), its length and its capacity.
func Slice() {
var mySlice []int // len 0, cap 0
printSlice(mySlice, "empty slice")
myOtherSlice := []int{1, 2, 3} // len 3, cap 3
printSlice(myOtherSlice, "non-empty slice")
length := 5
mySliceWithLength := make([]int, length) // len 5, cap 5
printSlice(mySliceWithLength, "slice with length")
capacity := 10
mySliceWithMakeAndCapacity := make([]int, length, capacity) // len 5, cap 10
printSlice(mySliceWithMakeAndCapacity, "slice with length and capacity")
}func SliceOperations() {
mySlice := []int{1, 2, 3}
myElement := mySlice[0] // Accessing an element
mySlice[2] = myElement * 3 // Replace an element
mySlice = append(mySlice, 4) // Append a new element at the end
mySlice[0], mySlice[1] = mySlice[3], mySlice[2] // Swap multiple elements
printSlice(mySlice, "slice after operations")
}What will be the content of mySlice after all operations?
func SliceIteration() {
mySlice := []int{1, 2, 3, 4}
sliceSum := 0
for i := 0; i < len(mySlice); i++ {
sliceSum += mySlice[i]
}
fmt.Println("This is the sum of my slice: ", sliceSum)
sliceMult := 1
for i := range mySlice {
sliceMult *= mySlice[i]
}
fmt.Println("This is the multiplication of my slice's elements: ", sliceMult)
for i, element := range mySlice {
fmt.Printf("This is the element %d from my slice: %d\n", i, element)
}
for _, element := range mySlice {
fmt.Println("Iterating on my slice: ", element)
}
}The blank identifier is represented by the underscore character _. It serves as an anonymous placeholder.
Remind that if you declare a variable, then you must use it somewhere in your code.
The blank identifier gives you the possibility to ignore a variable.
It also has another usage for importing a package.
You now have a function with a slice in inputs.
file/to/open.go$ go test .File to open:
Test your code:
fizzbuzz/step2/multi.gofunc multiFizzBuzz(numbers []int) []string {
results := make([]string, len(numbers))
for i, number := range numbers {
results[i] = fizzbuzz(number)
}
return results
}Maps are simple key-value data structure. You can associate a key of a defined type to a value of another type.
The key can be of any type for which the equality operator is defined.
The order in a map is random. While iterating on a map, you should not make assumption about the order of the keys or the values.
func Map() {
myMap := map[string]string{
"France": "Paris",
"Belgium": "Brussels",
"Spain": "Madrid",
}
fmt.Println("Capitals are: ", myMap)
myMapFromMake := make(map[string]string, 0)
fmt.Println("An empty map: ", myMapFromMake)
}func MapOperations() {
myMap := map[string]string{
"France": "Paris",
}
myMap["Belgium"] = "Brussels" // Assign a new element
myMap["France"] = "Lille" // Replace an element's value
franceCapital := myMap["France"] // Accessing to an element
fmt.Println("The new capital in France is: ", franceCapital)
switzerlandCapital, found := myMap["Switzerland"] // Access to an element and check existence
if found {
fmt.Println("The capital of Switzerland is: ", switzerlandCapital)
} else {
fmt.Println("Looks like we don't know the capital of Switzerland")
}
_, found = myMap["Belgium"] // Check existence
if found {
fmt.Println("Looks like we know the capital of Belgium!")
}
}func MapIteration() {
myMap := map[string]string{
"France": "Paris",
"Belgium": "Brussels",
}
for country := range myMap {
fmt.Printf("The capital of %s is %s\n", country, myMap[country])
}
for country, capital := range myMap {
fmt.Printf("The capital of %s is %s\n", country, capital)
}
for _, capital := range myMap {
fmt.Println("Oh! I know this capital: ", capital)
}
}You now have a function with a slice in inputs and a map in outputs.
file/to/open.go$ go test .File to open:
Test your code:
fizzbuzz/step3/multimap.gofunc multiFizzBuzzMap(numbers []int) map[int]string {
result := make(map[int]string, len(numbers))
for _, number := range numbers {
result[number] = fizzbuzz(number)
}
return result
}Since Go 1.18, thanks to generics, Go provides standard libraries to manipulate slices and maps:
Functions are specific types, defined by:
Function is a type, and as other types, can be assigned to a variable.
func EmptyFunc() {
fmt.Println("Hello from empty func")
}
func FuncWithInput(myString string) {
fmt.Println("Hello from func with input", myString)
}
func FuncWithInputAndOuput(name string) string {
return fmt.Sprintf("Hello %s", name)
}
func FuncWithMultipleInputsAndOuputs(myString string, myInt1, myInt2 int) (string, int) {
return fmt.Sprintf("Hello %s", myString), myInt1 + myInt2
}func FuncWithNamedOutput() (result string) {
result = "Hello there"
return
}
func FuncSumWithVariadicInput(numbers ...int) (result int) {
for _, number := range numbers {
result += number
}
return
}
func CallFuncWithVariadicInput() {
result := FuncSumWithVariadicInput(1, 2, 3)
fmt.Println("Result from sum function:", result)
result = FuncSumWithVariadicInput(1, 2, 3, 4, 5)
fmt.Println("Result from sum function:", result)
numbers := []int{10, 20, 30}
result = FuncSumWithVariadicInput(numbers...)
fmt.Println("Result from sum function:", result)
}func Pointers() {
myInt := 3
myIntPointer := new(int)
myIntPointer = &myInt
fmt.Println("Pointer to int: ", myIntPointer)
// Print "Pointer to int: 0xc0000ac0b8"
fmt.Println("Int from pointer: ", *myIntPointer)
// Print "Pointer to int: 3"
}A pointer type denotes the set of all pointers to variables of a given type, called the base type of the pointer. The value of an uninitialized pointer is nil.
type Person struct {
Name string
Age int
}A struct is a sequence of named elements, called fields, each of which has a name and a type.
Within a struct, field names must be unique.
func InitializeStruct() {
jessie := Person{
Name: "Jessie",
Age: 17,
}
james := Person{"James", 17}
fmt.Println("Jessie's age is ", jessie.Age)
fmt.Println("Famous Team Rocket members: ", jessie, james)
}Initialisation can be done with or without naming the fields.
func (p *Person) HappyBirthday() {
p.Age = p.Age + 1
}
func (p Person) YearOfBirth() int {
return time.Now().Year() - p.Age
}A method is a function with a receiver. A method declaration binds an identifier, the method name, to a method, and associates the method with the receiver's base type.
Do you notice a difference between the two methods?
Passing the receiver as value creates a copy of the receiver. Therefore, any modification on the receiver will be discarded at the end of the method.
This is why you should pass a pointer as receiver on a method that modify the receiver.
type UniquePerson struct {
cardIdNumber string // Is not visible outside the package
Name string // Visible and modifiable outside the package
Age int // Visible and modifiable outside the package
}Any field starting with an uppercase is public, ie. visible to any package, for read and write.
Any field starting with a lowercase is private, ie. visible only inside the package where the type struct has been defined.
How to properly instantiate this struct?
func NewUniquePerson(cardIdNumber, name string, age int) *UniquePerson {
return &UniquePerson{
cardIdNumber: cardIdNumber,
Name: name,
Age: age,
}
}Provide a constructor to your struct inside your package.
Any struct, function, method, field or global variable starting with an uppercase letter is public and visible (read or modification) to everyone.
Any struct, function, method, field or global variable starting with a lowercase letter is private and visible only inside the package.
You now have a struct with two fields, a number and a result.
file/to/open.go$ go testFile to open:
Test your code:
fizzbuzz/step4/fizzstruct.gofunc (f *FizzBuzz) Compute() {
f.result = fizzbuzz(f.number)
}Let's write a HTTP handler for an API!
GET /?number=15 must return a string in the HTTP response.
The algorithm is described in comments in the code.
file/to/open.go$ go test .
# or
$ go run cmd/api/main.go
$ curl 'localhost:8080/?number=15'File to open:
Test your code:
fizzbuzz/step4/server.goThe http package: https://pkg.go.dev/net/http
Get query parameters from the request struct: https://pkg.go.dev/net/http#Request
Transform string to number: https://pkg.go.dev/strconv
Write the HTTP response: https://pkg.go.dev/net/http#ResponseWriter
func FizzBuzzHandler(w http.ResponseWriter, r *http.Request) {
// Get query parameter "number" from URL
numberString := r.URL.Query().Get("number")
// Transform number from string to int
number, err := strconv.Atoi(numberString)
// If invalid input, write response header HTTP 400 then return
if err != nil {
w.WriteHeader(http.StatusBadRequest)
return
}
// Compute FizzBuzz on number
result := fizzbuzz(number)
// Write response header HTTP 200
w.WriteHeader(http.StatusOK)
// Write result on response
fmt.Fprint(w, result)
}A Tour of Go is the perfect next step: https://go.dev/tour/welcome/1
You can also work on some kata:
Write your feedbacks on the form, and ask me if you have any question!