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
default
case - 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
go
version (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.sum
file 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.