292 lines
8.5 KiB
Markdown
292 lines
8.5 KiB
Markdown
# Go Basics, Part 1 — Syntax, Types, and Control Flow
|
|||
|
|
|
||
|
|
This is the first of three "Go Basics" lessons. If you've never written Go
|
||
|
|
before, do all three before starting Lesson 1 of the main course. If you
|
||
|
|
already know another programming language (Python, JavaScript, PHP, Java,
|
||
|
|
C, etc.), you'll move through this fast — Go is a small, simple language
|
||
|
|
on purpose.
|
||
|
|
|
||
|
|
## 1. Installing Go and running your first program
|
||
|
|
|
||
|
|
Download and install Go from [go.dev/dl](https://go.dev/dl/). Confirm it worked:
|
||
|
|
|
||
|
|
```bash
|
||
|
|
go version
|
||
|
|
```
|
||
|
|
|
||
|
|
You should see something like `go version go1.26.x`.
|
||
|
|
|
||
|
|
Make a folder and your first program:
|
||
|
|
|
||
|
|
```bash
|
||
|
|
mkdir hello && cd hello
|
||
|
|
go mod init hello
|
||
|
|
```
|
||
|
|
|
||
|
|
`go mod init hello` creates a `go.mod` file — this marks the folder as a
|
||
|
|
**Go module** (a self-contained project with its own dependencies). We'll
|
||
|
|
explain modules properly in Part 3; for now, just know every Go project
|
||
|
|
needs one.
|
||
|
|
|
||
|
|
**`main.go`**
|
||
|
|
```go
|
||
|
|
package main
|
||
|
|
|
||
|
|
import "fmt"
|
||
|
|
|
||
|
|
func main() {
|
||
|
|
fmt.Println("hello, world")
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
Run it:
|
||
|
|
```bash
|
||
|
|
go run .
|
||
|
|
```
|
||
|
|
|
||
|
|
You should see `hello, world` printed. Let's break down every single piece
|
||
|
|
of that file, since you'll type this pattern constantly:
|
||
|
|
|
||
|
|
- `package main` — every Go file starts by declaring which **package** it
|
||
|
|
belongs to. A package is just a folder of `.go` files that are compiled
|
||
|
|
together and can freely call each other's code. `package main` is
|
||
|
|
special: it means "this produces a runnable program," not a reusable
|
||
|
|
library.
|
||
|
|
- `import "fmt"` — pulls in the standard library's `fmt` package (short
|
||
|
|
for "format"), which has functions for printing text and formatting
|
||
|
|
strings.
|
||
|
|
- `func main()` — the function named `main`, inside `package main`, is the
|
||
|
|
**entry point**. When you run the compiled program, execution starts
|
||
|
|
here. There must be exactly one `main` function in `package main`.
|
||
|
|
- `fmt.Println("hello, world")` — calls the `Println` function from the
|
||
|
|
`fmt` package (note the dot: `package.Function`), passing it the string
|
||
|
|
`"hello, world"`. `Println` prints its arguments followed by a newline.
|
||
|
|
|
||
|
|
Two ways to run Go code:
|
||
|
|
- `go run .` (or `go run main.go`) — compiles and runs immediately,
|
||
|
|
doesn't leave a binary behind. Good for development.
|
||
|
|
- `go build .` — compiles into an actual executable file (e.g. `hello` or
|
||
|
|
`hello.exe`) that you can run directly (`./hello`) without Go installed
|
||
|
|
on the machine that runs it. This is what you'd do to ship the program.
|
||
|
|
|
||
|
|
## 2. Variables and basic types
|
||
|
|
|
||
|
|
Go is **statically typed**: every variable has a fixed type, decided
|
||
|
|
either explicitly or by inference, and that type never changes.
|
||
|
|
|
||
|
|
```go
|
||
|
|
package main
|
||
|
|
|
||
|
|
import "fmt"
|
||
|
|
|
||
|
|
func main() {
|
||
|
|
// Explicit type
|
||
|
|
var age int = 30
|
||
|
|
|
||
|
|
// Type inferred from the value (int, since 30 has no decimal point)
|
||
|
|
var name = "Hamid"
|
||
|
|
|
||
|
|
// The short declaration operator ":=" — declares AND assigns in one
|
||
|
|
// step, inferring the type. This is by far the most common way to
|
||
|
|
// declare variables inside a function body.
|
||
|
|
city := "Tehran"
|
||
|
|
height := 1.78 // inferred as float64
|
||
|
|
|
||
|
|
fmt.Println(age, name, city, height)
|
||
|
|
|
||
|
|
// Reassignment - no "var" or ":=" needed, the variable already exists
|
||
|
|
age = 31
|
||
|
|
fmt.Println(age)
|
||
|
|
|
||
|
|
// Multiple variables at once
|
||
|
|
var x, y int = 1, 2
|
||
|
|
a, b := 3, 4
|
||
|
|
fmt.Println(x, y, a, b)
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
Key rules:
|
||
|
|
- `:=` can ONLY be used to declare a **new** variable (usually inside a
|
||
|
|
function). It's shorthand for `var x = value` with the type inferred.
|
||
|
|
- `var` can be used with or without an initial value: `var count int`
|
||
|
|
declares `count` as an `int` with the **zero value** `0` (Go always
|
||
|
|
initializes variables — there's no "undefined" or garbage value).
|
||
|
|
- You cannot declare a variable and never use it — Go's compiler will
|
||
|
|
refuse to build code with an unused local variable. This trips up
|
||
|
|
everyone coming from other languages at first.
|
||
|
|
|
||
|
|
### The built-in types you'll use constantly
|
||
|
|
|
||
|
|
| Type | Example | Notes |
|
||
|
|
|---|---|---|
|
||
|
|
| `int` | `42` | Platform-dependent size (64-bit on modern machines); use this by default for whole numbers |
|
||
|
|
| `string` | `"hello"` | UTF-8 text, immutable |
|
||
|
|
| `bool` | `true`, `false` | |
|
||
|
|
| `float64` | `3.14` | Default type for decimal numbers |
|
||
|
|
| `byte` | | Alias for `uint8`, used for raw binary data |
|
||
|
|
| `[]byte` | | A "slice of bytes" — how Go represents raw binary data (we cast strings to this constantly, e.g. for password hashing) |
|
||
|
|
|
||
|
|
Zero values (what a variable holds if declared without an initial value):
|
||
|
|
`int` → `0`, `string` → `""` (empty string), `bool` → `false`, pointers →
|
||
|
|
`nil` (explained in Part 2).
|
||
|
|
|
||
|
|
### Type conversion
|
||
|
|
|
||
|
|
Go **never** silently converts between types (unlike JavaScript or PHP).
|
||
|
|
You must convert explicitly:
|
||
|
|
|
||
|
|
```go
|
||
|
|
var i int = 42
|
||
|
|
var f float64 = float64(i) // must explicitly convert int -> float64
|
||
|
|
var s string = fmt.Sprintf("%d", i) // int -> string via formatting
|
||
|
|
|
||
|
|
id := "123"
|
||
|
|
// s, err := strconv.Atoi(id) // string -> int, using the strconv package
|
||
|
|
```
|
||
|
|
|
||
|
|
You'll see this constantly in the main course, e.g. `int(id)` when
|
||
|
|
converting a database's `int64` auto-increment ID into our own `int`
|
||
|
|
field.
|
||
|
|
|
||
|
|
## 3. `if`, `for`, and `switch`
|
||
|
|
|
||
|
|
Go has exactly one looping construct — `for` — no `while`, no `do-while`.
|
||
|
|
It also does **not** use parentheses around conditions, but curly braces
|
||
|
|
`{ }` are always required (even for a single-line body).
|
||
|
|
|
||
|
|
### `if`
|
||
|
|
|
||
|
|
```go
|
||
|
|
age := 20
|
||
|
|
|
||
|
|
if age >= 18 {
|
||
|
|
fmt.Println("adult")
|
||
|
|
} else if age >= 13 {
|
||
|
|
fmt.Println("teenager")
|
||
|
|
} else {
|
||
|
|
fmt.Println("child")
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
A very common Go idiom: declaring a variable that's scoped ONLY to the
|
||
|
|
`if`/`else` block, often used with error handling (you'll see this
|
||
|
|
constantly starting in Part 2):
|
||
|
|
|
||
|
|
```go
|
||
|
|
if err := doSomething(); err != nil {
|
||
|
|
fmt.Println("failed:", err)
|
||
|
|
}
|
||
|
|
// err doesn't exist out here — it was scoped to the if statement
|
||
|
|
```
|
||
|
|
|
||
|
|
### `for` — Go's only loop
|
||
|
|
|
||
|
|
```go
|
||
|
|
// Classic three-part loop (like C's for)
|
||
|
|
for i := 0; i < 5; i++ {
|
||
|
|
fmt.Println(i)
|
||
|
|
}
|
||
|
|
|
||
|
|
// "while" style - just the condition
|
||
|
|
count := 0
|
||
|
|
for count < 3 {
|
||
|
|
fmt.Println("counting:", count)
|
||
|
|
count++
|
||
|
|
}
|
||
|
|
|
||
|
|
// Infinite loop - break out manually
|
||
|
|
for {
|
||
|
|
fmt.Println("runs forever until break")
|
||
|
|
break
|
||
|
|
}
|
||
|
|
|
||
|
|
// Looping over a collection (slices, maps - covered in Part 3)
|
||
|
|
names := []string{"alice", "bob", "carol"}
|
||
|
|
for index, name := range names {
|
||
|
|
fmt.Println(index, name)
|
||
|
|
}
|
||
|
|
// If you don't need the index, use _ (the "blank identifier") to
|
||
|
|
// explicitly discard it - Go's compiler complains about unused
|
||
|
|
// variables, and _ is the escape hatch:
|
||
|
|
for _, name := range names {
|
||
|
|
fmt.Println(name)
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
You'll see `_` constantly throughout the main course — any time a
|
||
|
|
function returns something you genuinely don't need, `_` discards it
|
||
|
|
without triggering an "unused variable" error.
|
||
|
|
|
||
|
|
### `switch`
|
||
|
|
|
||
|
|
```go
|
||
|
|
day := "Monday"
|
||
|
|
|
||
|
|
switch day {
|
||
|
|
case "Saturday", "Sunday":
|
||
|
|
fmt.Println("weekend")
|
||
|
|
default:
|
||
|
|
fmt.Println("weekday")
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
Unlike C/Java, Go's `switch` cases do **not** fall through by default —
|
||
|
|
each case automatically breaks after its block, no `break` statement
|
||
|
|
needed.
|
||
|
|
|
||
|
|
## 4. Strings, formatting, and comments
|
||
|
|
|
||
|
|
```go
|
||
|
|
name := "Hamid"
|
||
|
|
age := 31
|
||
|
|
|
||
|
|
// Println - space-separated, newline at the end
|
||
|
|
fmt.Println("name:", name, "age:", age)
|
||
|
|
|
||
|
|
// Printf - C-style format string, YOU add the newline with \n
|
||
|
|
fmt.Printf("name: %s, age: %d\n", name, age)
|
||
|
|
|
||
|
|
// Sprintf - same as Printf but returns a string instead of printing it
|
||
|
|
message := fmt.Sprintf("hello %s, you are %d years old", name, age)
|
||
|
|
fmt.Println(message)
|
||
|
|
```
|
||
|
|
|
||
|
|
Common format verbs you'll use throughout the course:
|
||
|
|
| Verb | Meaning |
|
||
|
|
|---|---|
|
||
|
|
| `%s` | string |
|
||
|
|
| `%d` | integer |
|
||
|
|
| `%f` | float |
|
||
|
|
| `%v` | "default" representation of any value — great for debugging |
|
||
|
|
| `%+v` | like `%v` but includes struct field names |
|
||
|
|
| `%w` | wraps an error (covered in Part 3) — only valid with `fmt.Errorf` |
|
||
|
|
|
||
|
|
Comments:
|
||
|
|
```go
|
||
|
|
// A single-line comment
|
||
|
|
|
||
|
|
/*
|
||
|
|
A multi-line comment.
|
||
|
|
*/
|
||
|
|
|
||
|
|
// A comment directly above a function/type, with no blank line between,
|
||
|
|
// is a DOC comment - tools display it as that function's documentation.
|
||
|
|
// This project's code uses these constantly.
|
||
|
|
func DoSomething() {}
|
||
|
|
```
|
||
|
|
|
||
|
|
## 5. Try it yourself
|
||
|
|
|
||
|
|
Before moving to Part 2, write a small standalone program (new folder,
|
||
|
|
`go mod init practice`, `main.go`) that:
|
||
|
|
|
||
|
|
1. Declares a `string` name and an `int` age using `:=`.
|
||
|
|
2. Uses `if`/`else if`/`else` to print a different message depending on
|
||
|
|
the age (e.g. "minor", "adult", "senior" for under 18 / under 65 / 65+).
|
||
|
|
3. Uses a `for` loop to print the numbers 1 through 10.
|
||
|
|
4. Uses `fmt.Printf` to print the name and age formatted into one sentence.
|
||
|
|
|
||
|
|
Run it with `go run .`. Once this feels natural, move to Part 2 —
|
||
|
|
functions, structs, and pointers, which is where Go starts looking like
|
||
|
|
the code you'll write in the actual course.
|