README ¶
Let's Go workshop
Introduction
This workshop aims to give an introduction of the Go language by writing code and practising the language. Step by step more code will be introduced and the final product will be a tool that creates GIFs from an input text.
Tools needed for the workshop
- Go
- Download at go.dev
- Verify that it's working by running
go version
in the command line
- The IDE of your choice
- Visual Studio Code with the Go extension
- Goland (from JetBrains)
- Vim plugin https://github.com/fatih/vim-go
- Git
- To commit the different milestones
- pkg.go.dev
- This is the link to the documentation of the various Go packages available
- Inside or outside the standard library
- Useful to learn about each symbol (types, functions, methods, and so on) and what they do for each package. It usually contains examples of usages of these symbols as well
- This is the link to the documentation of the various Go packages available
Milestones
Here is the list of different milestones we are going to acheive during the workshop.
Hello, World! (milestone 0)
For the first step you are going to write the hello world, and this is also going to be the occasion to setup your Go project.
Steps
- Create a folder, e.g. lets-go-workshop; This will contain the code to write for the workshop
- Go inside the folder and run
go mod init
from the terminal - (optional) run
git init
if you want to store the code for the different milestones in a different commit - Open your IDE inside the workshop folder
- Create a
main.go
file - Write the following code:
package main
import "fmt"
func main() {
fmt.Println("Hello, World!")
}
- Run
go run main.go
Explanations
The command go mod init
initializes a project making it a Go module. A Go module represents a set of Go files that are supposed to be shipped altogether: e.g. an application or a library.
Every Go application starts at a main()
function located in a main.go
file.
Every Go file resides in a package
, this information is declared as the first instruction of the file. The main function should reside in package main.
Packages can contain functions, constants, custom types and so on inside a specific namespace; to use a specific package it needs to be imported with the import
keyword. An unused import results in a compilation error.
Any exported/public symbol inside a package can be invoked in the form of package name.symbol name
. For example, in the call fmt.Println
, fmt is the package name and Println is the symbol name.
In Go ;
is not mandatory at the end of each statement.
Milestone 1
For this step you are going to improve on the hello world with some code that creates a file and writes the hello world on it. The goal is to see variable declaration and initialization, conditionals with the if statement, to start interacting with some packages the standard library and to introduce few Go specific features: the blank identifier, the multi-value returns, the error type and the defer keyword.
Steps for milestone 1
- Create a file (use the
os
package), write the "hello, world!" string to the file (use thefmt
package), close the file - if there are any errors during the file creation, log them and exit (use the
log
package) - Use the defer keyword to defer the execution of the Close() call
- Use the command line arguments to take the text to print (use the
flag
package) - Run
go run main.go "Hello, World!"
Notes for milestone 1
Use pkg.go.dev to search the content of the packages os
, fmt
, log
and flag
.
To declare and assign variables you can use two tools:
- The
:=
operator (declare and assign)- a := 1
- The
var
keyword (declare), followed potentially by the=
operator (assign)- var a int = 1
When the compiler can infer the type of a variable it can be omitted:
- var a int = 1 // => var a = 1
- a := 1
- in this statement the type is already omitted
When the type name is present in a statement, it always goes after the name:
- var a int // 👍
- var int a // ❌ (compilation error)
You can declare and/or assign multiple variables, of the same or different type, at the same time:
- a, b, c := "hello", 1, "world"
- f, err := os.Create("myFile") // os.Create is a function that returns 2 values
If you don't need a specific value during an assignment you can ignore it using the blank identifier _
. Any unused variables are considered complier errors.
- f, _ := os.Create("myFile") // following the previous example, I just need the value of f from the function
The if statement is similar to most languages, here are some differences:
- No
()
are required around the condition {}
are always required around the code to execute if the condition is true, even if it fits in one line
The defer keyword instructs the function that follows to be called at the end of the function where it is deferred.
Generally, in a multi-value return the second argument or last argument is of type error which is an interface that declares one method: func Error() string
A value of a specific interface type can be compared only with another value of the same type or with nil
which means: no value for that interface.
Milestone 2
In this milestone you'll create your first image (a PNG); in doing so you'll learn about creating your own package and function, invoke them and learn about for loops, types, zero values and the meaning of exported and unexported.
Steps for milestone 2
- Create a new folder named myimage; in Go, this is a new package
- Inside the myimage folder create a new file called myimage.go
- Write a function
func New(l, h int, c color.RGBA) *image.Paletted
inside myimage.go that draws a rectangle of the specified size and color (use theimage
package, see functionsRect
,NewPaletted
and theSet
method of thePaletted
type) - Change the main.go to first call the function that draws the rectangle then use
png.Encode
to save the rectangle image in a PNG file - Run
go run main.go
As a bonus you can add more input flags for the properties of the rectangle (length, height and color) and the output file name.
Notes for milestone 2
A package name and its containing folder do not have to have matching names, but it's good practice to do so.
There are no specific keywords to define the scope of a Go symbol:
- Name is UPPERCASE
- Symbol is exported, which means available to all packages (public-level scope)
- Name is lowercase
- Symbol is unexported, which means available to the package where it is located (package-level scope)
There three kind of types in Go:
- Primitives
- Internal types
- Custom types
Primitives in Go are bool (true or false), the literal type string and numeric types rune, byte (rune and byte can also represent literals), uint, uint8, uint16, uint32, uint64, int, int8, int16, int32, int64, float32, float64, complex64, complex128).
Internal types are array, function, slice, map, and channel.
Custom types are all Go types declared with the type
and/or struct
keywords.
For example:
type Age int
type Person struct {
name string
age Age
}
Every variable is initialized in Go at the declaration time; if not explicitly initialized in the code, they will be initialized by Go with the zero value for the type of that variable.
Here is the zero value list:
var (
b bool // false
i int // 0 (same for all numeric types)
s string // ""
p Person // Person{}, all custom types are initialized with all fields set to their zero values
v map[int]string // nil, also valid for all internal types
)
Looping in Go is similar to most languages with some differences:
for
is the only keyword used for loops- No
()
are required around the loop instructions {}
are always required around the code block to execute inside the loop, even if it fits in one line- for condition {} replaces the while statement; there are no do/while statements in Go
- for true {} represents an infinite loop (to be interrupted with a
break
statement
Using flags in Go requires the usage of the flag
package:
- first define all flags using the functions
Int
,String
,Bool
and so on, according to the type of the data you request in input- They all take flag name, default value and short description as inputs and return pointers of the same type as in the function name
- They can be visualized when running
go run main.go --help
, without needing to define a flag for the help message
- then call the function
flag.Parse()
to parse all flags defined above
Pointers in Go are similar to C/C++. Use *
declare or dereference them, use &
to take the address of a value.
Milestone 3
In this milestone you'll write your first message on the previously created image; in doing so you'll learn interacting with external packages from the standard library and see how to import them in your project.
Steps for milestone 3
- Write a function
func Write(dst draw.Image, text string, c color.RGBA, fontPath string, fontSize float64) error
inside myimage.go that draws a text on the rectangle created in milestone 2 - Import the package
github.com/golang/freetype
inside myimage.go - In the terminal, at the root of the project run
go mod tidy
- Write the code to create and configure the
freetype.Context
object - Call the
Write
function inside the main function, - Run
go run main.go
As a bonus you can:
- keep adding input flags for the color of the string, the font file path and the font size.
- create a function that gives the appropriate size for the rectangle by taking into account the font size and the length of the text to draw
Notes for milestone 3
The difference between packages from the standard library and the external ones can be seen from its naming when getting or importing them. In fact, the main difference is that external packages include a complete path to external package containing:
- the registry: github, gitlab, bitbucket, and so on
- the package author: be it a username or a project group name
- the package name and path to subpackage if relevant
- in local it will be the path to the package
the function Join
of the strings
package is a useful tool to join several strings, organized into a slice (array), in a single string.
Documentation ¶
There is no documentation for this package.