Go (Golang)
Go (Golang) is a statically-typed, compiled programming language by Google that has strong support for concurrent programming. Its standard library (with features such as built-in HTTP server and JSON support) makes it a suitable backend language for scalable and high-performance applications.
Go's concurrency model (using Goroutines and Channels) can handle multiple tasks simultaneously with minimal complexity.
Keywords
package / import
The first line of every .go file specifies the package name.
package main // declare main package (must contain main() function)
/* packages can be imported */
import "package"
import (
"fmt"
"time"
_ "embed" // mark package as unused
myname "github.com/path/to/library"
)
// private/public identifiers via capitalization
var foo := 3 // private to package
var Bar := 3 // will be exported
func DoThing() { /* ... */ } // can be called outside package
for, switch, range (Control flow)
/* switch */
switch state {
case 1:
/* ... */
goto label;// jump to label
case 2:
/* ... */
fallthrough
case 3:
label: // goto label declaration
/* ... */
break // always implicitly there
default:
}
/* loops */
for count < 5 {
if count == 3 {
continue
}
}
for { /* ... */ } // like a while(true) loop
/* ranges */
scores := map[string]int{"Alice": 90, "Bob": 85}
for name, score := range scores {
/* ... */
}
Types
/* types */
var // declare a variable
const // declare a constant
map[string] // declare map type (hashtable/dictionary)
type Person struct { // define struct type
name string
age int
}
interface{} // any type
any // alias for interface{}
/* enums in go: use "iota" to declare constants */
type Weekday int
const (
_ = iota // ignore first value (iota == 0)
MONDAY Weekday = iota // == 1
TUESDAY // == 2
WEDNESDAY
THURSDAY
FRIDAY
SATURDAY
SUNDAY
)
Functions/Interfaces
// functions
func example(var a int) (int, error) {
defer fmt.Printf("Finished\n") // defer function call to end of function
return a, nil
}
/* interfaces */
type Shape interface { // define interface named Shape
Area() float64
}
type Circle struct {
radius float64
}
func (c Circle) Area() float64 { // implement interface for Circle
return 3.14 * c.radius * c.radius
}
go, chan, select (Concurrency)
/* goroutines/channels */ go // start a goroutine chan // declare channel type select // wait on multiple channel operations
Concepts
goroutine
A goroutine is a lightweight thread managed by the Go runtime.
package main
import (
"fmt"
"time"
)
func greet(name string) {
fmt.Printf("Hello, %s!\n", name)
}
func main() {
go greet("Alice") // start goroutine
go greet("Bob") // start another
time.Sleep(1 * time.Second) // wait for goroutines to finish
fmt.Println("Finished.")
}
Using WaitGroup to wait for goroutine
package main
import (
"fmt"
"sync"
"time"
)
func worker(id int, wg *sync.WaitGroup) {
defer wg.Done() // notify that the goroutine is done
fmt.Printf("Worker %d starting\n", id)
time.Sleep(2 * time.Second) // simulate work
fmt.Printf("Worker %d done\n", id)
}
func main() {
var wg sync.WaitGroup
for i := 1; i <= 3; i++ {
wg.Add(1) // increment WaitGroup counter
go worker(i, &wg) // start goroutine
}
wg.Wait() // wait for all goroutines to finish
fmt.Println("All workers completed.")
}
chan (Channels)
Channels enable communication between goroutines.
package main
import "fmt"
func calculateSquare(num int, ch chan int) {
ch <- num * num // send result to channel
}
func main() {
ch := make(chan int) // create channel
for i := 1; i <= 5; i++ {
go calculateSquare(i, ch) // start goroutine
}
for i := 1; i <= 5; i++ {
fmt.Println("Square:", <-ch) // receive result from the channel
}
}
Channels can be directional:
var ch chan int // bidirectional (send & receive) var sendCh chan<- int // send-only channel var recvCh <-chan int // receive-only channel
Channels can be buffered or unbuffered:
- Buffered for synchronous, blocking communication
- Unbuffered for asynchronous, non-blocking communication
- Unbuffered better for coordination, buffered better for throughput
- Buffered can mask deadlocks that unbuffered would reveal
// Unbuffered channel ch1 := make(chan int) // - Synchronous communication // - Sender blocks until receiver takes value // - Receiver blocks until sender provides value // - Provides guaranteed delivery and synchronization point // Buffered channel ch2 := make(chan int, 5) // Buffered with capacity 5 // - Asynchronous until buffer fills // - Sender only blocks when buffer is full // - Receiver blocks only when buffer is empty // - Decouples sender and receiver timing
If no more messages need to be sent over the channel, it can be closed:
// sending side
close(ch) // close channel
val, ok := <-ch
if !ok {
// channel has closed (buffered channels stay open until emptied)
}
select
The select statement enables concurrent operations on channels.
Characteristics:
- Blocks until a channel operation can proceed
- Randomly selects if multiple cases are ready
- Can include non-blocking
defaultcase - Often used in goroutines for concurrent operations
- Can be combined with timeouts using
time.After()
select {
case <-ch1:
// Execute if data received from ch1
case x := <-ch2:
// Execute if data received from ch2, store in x
case ch3 <- value:
// Execute if data can be sent to ch3
case <-time.After(1 * time.Second):
fmt.Println("Timed out")
default:
// Execute if no other case is ready (optional)
}
Module System
go.mod:
The go.mod is the config file for a Go module.
- Declares module path (
module mymodule). - Declares required
goversion (go 1.21.1) - Lists module's dependencies (and versions) with
require (...) - Can include replace directives (useful for testing)
- Can also exclude modules
go.sum:
- The
go.sumfile is used to manage dependencies. - It contains the url for the module, its version and a cryptographic checksum.
- It is generated and updated when running
go get,go mod tidy, orgo build.
Libraries
net/http
The standard library net/http provides HTTP client/server implementations. It
can be used to build web apps, APIs, and handling HTTP requests/responses.
Hello World Server example
package main
import (
"fmt"
"net/http"
)
func handler(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "Hello, World!")
}
func main() {
//http.Handle(pattern string, handler Handler)
//http.HandleFunc(pattern string, handler func(ResponseWriter, *Request))
http.HandleFunc("/", handler)
//http.ListenAndServe(addr string, handler Handler) error
http.ListenAndServe(":8080", nil)
}
html/template
Go provides templating utilities for text with text/template. For templating
HTML, html/template should be used to ensure special characters are rendered as
plaintext (preventing Cross-Site Scripting (XSS) attacks).
package main
import (
"html/template"
"net/http"
)
// Data structure to hold the template data
type Data struct {
Name string
Count int
}
// HTML template string
const htmlTmplStr = `<h1>Hello, {{.Name}}!</h1><p>You have {{.Count}} new messages.</p>`
// HTTP handler function
func handler(w http.ResponseWriter, r *http.Request) {
tmpl, err := template.New("greeting").Parse(htmlTmplStr)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
data := Data{Name: "Alice", Count: 5}
err = tmpl.Execute(w, data)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
}
}
func main() {
http.HandleFunc("/", handler)
http.ListenAndServe(":8080", nil)
}
database/sql
package main
import (
"database/sql"
"fmt"
"log"
"time"
_ "github.com/lib/pq" // PostgreSQL driver
)
type User struct {
ID int
Email string
CreatedAt time.Time
}
func main() {
// Connect to database
connStr := "postgres://user:password@localhost/dbname?sslmode=disable"
db, _ := sql.Open("postgres", connStr)
defer db.Close()
// create table
db.Exec(`
CREATE TABLE IF NOT EXISTS users (
id SERIAL PRIMARY KEY,
email TEXT NOT NULL UNIQUE,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
)
`)
// inserting data
email := "test@example.com"; var id int
err = db.QueryRow("INSERT INTO users (email) VALUES ($1) RETURNING id", email,).Scan(&id)
fmt.Printf("Inserted user with ID: %d\n", id)
// Query single row
var user User
err = db.QueryRow("SELECT id, email, created_at FROM users WHERE id = $1", id).Scan(&user.ID, &user.Email, &user.CreatedAt)
fmt.Printf("User: %+v\n", user)
// Query multiple rows
rows, err := db.Query("SELECT id, email, created_at FROM users")
defer rows.Close()
fmt.Println("\nAll users:")
for rows.Next() {
var u User
if err := rows.Scan(&u.ID, &u.Email, &u.CreatedAt); err != nil {
log.Fatal(err)
}
fmt.Printf("%+v\n", u)
}
// use prepared statement
stmt, _ := db.Prepare("UPDATE users SET email = $1 WHERE id = $2")
defer stmt.Close()
res, _ := stmt.Exec("updated@example.com", id)
rowsAffected, _ := res.RowsAffected()
fmt.Printf("\nUpdated %d rows\n", rowsAffected)
// Transaction example
tx, err := db.Begin()
_, err = tx.Exec("INSERT INTO users (email) VALUES ($1)", "transaction@example.com")
if err != nil { tx.Rollback() }
err = tx.Commit()
}
Idioms
Struct Embedding
Add types without a name to a struct to embed all of its members directly. The type can then call methods of that embedded type directly.
import "sync"
type Message struct {
sync.RWMutex
text string
}
func main() {
msg := Message{}
msg.Lock(); defer msg.Unlock();
}
Shorthand for Error Handling
if err := writer.Close(); err != nil {
panic(err)
}
Interface Satisfaction Check
Ensure a type implements interfaces at compile time:
var _ io.Reader = (*MyType)(nil)
If MyType doesn't implement Read(p []byte) (n int, err error), the compiler will
throw an error.