~/dev $

go cheatsheet

— compiled · statically typed · concurrent

Syntax Basics

Package deklarieren
package main
Import
import "fmt"
Multi-Import
import ( … )
Einstiegspunkt
func main() {}
Variable (inferiert)
x := 42
Variable (explizit)
var x int = 42
Konstante
const Pi = 3.14
Mehrere Variablen
a, b := 1, 2
Ausgabe
fmt.Println("hi")
Formatiert
fmt.Printf("%v", x)
String formatieren
fmt.Sprintf(…)

Typen

Integer
int int8 int64
Unsigned
uint uint8 uint64
Float
float32 float64
String
string
Boolean
bool
Byte / Rune
byte rune
Array (feste Länge)
[3]int{1,2,3}
Slice (dynamisch)
[]int{1,2,3}
Map
map[string]int{}
Pointer
*int &x
Any-Typ
any interface{}
Type Alias
type Meter float64
Type Cast
int(x) float64(y)

Control Flow

if / else
if x > 0 { … }
if mit init
if v, ok := …; ok {}
switch
switch x { case … }
switch ohne Ausdruck
switch { case x>0: }
for (klassisch)
for i:=0; i<n; i++ {}
while-Äquivalent
for x < 10 {}
Endlosschleife
for {}
range über Slice
for i, v := range s {}
range über Map
for k, v := range m {}
break / continue
break continue
defer
defer f.Close()
panic / recover
panic(…) recover()

Funktionen

Einfach
func add(a, b int) int
Multi-Return
func f() (int, error)
Named Return
func f() (n int)
Variadic
func sum(n ...int)
Anonym / Lambda
f := func(x int) int
Sofort aufrufen (IIFE)
func(){ … }()
Error-Pattern
val, err := f()
Error ignorieren
val, _ := f()
Error erstellen
errors.New("msg")
Formatierter Error
fmt.Errorf("…%w", err)
Error wrapping prüfen
errors.Is / errors.As

Slices & Maps

Slice erstellen
make([]int, len, cap)
Anhängen
s = append(s, val)
Slices zusammenführen
append(a, b...)
Sub-Slice
s[1:4]
Kopieren
copy(dst, src)
Länge / Kapazität
len(s) cap(s)
Map erstellen
make(map[K]V)
Wert lesen
v := m["key"]
Existenz prüfen
v, ok := m["key"]
Eintrag löschen
delete(m, "key")
Über Map iterieren
for k, v := range m

Goroutines & Channels

Goroutine starten
go f()
Anonyme Goroutine
go func(){ … }()
Channel erstellen
ch := make(chan int)
Gepufferter Channel
make(chan int, 10)
Senden
ch <- 42
Empfangen
v := <-ch
Channel schließen
close(ch)
select (non-blocking)
select { case … }
WaitGroup warten
wg.Wait()
Mutex sperren
mu.Lock() mu.Unlock()
Context mit Timeout
context.WithTimeout(…)
Context abbrechen
cancel()

Interfaces

Interface definieren
type Stringer interface{}
Methode in Interface
String() string
Impl. ist implizit
— kein "implements"
Type Assertion
v.(MyType)
Safe Assertion
v, ok := x.(MyType)
Type Switch
switch v := x.(type)
Wichtige Std-Interfaces
fmt.Stringer
String() string
io.Reader
Read([]byte)
io.Writer
Write([]byte)
error
Error() string

Tooling & CLI

Ausführen
go run main.go
Bauen
go build ./...
Testen
go test ./...
Tests verbose
go test -v -run Name
Formatieren
go fmt ./...
Linter
go vet ./...
Dokumentation
go doc pkg.Symbol
Modul initialisieren
go mod init name
Abhängigkeit hinzufügen
go get pkg@version
Aufräumen
go mod tidy
Generics Constraint
comparable any
Race Detector
go test -race

Go — Philosophie & Design-Prinzipien

Entstehung & Ziel

Go wurde 2009 bei Google von Robert Griesemer, Rob Pike und Ken Thompson entwickelt — als Antwort auf die Komplexität von C++ und die langsamen Compile-Zeiten großer Codebases. Das Ziel war eine Sprache, die sich wie eine Systemsprache anfühlt, aber so einfach schreibbar ist wie ein Skript.

Go ist opinionated by design: Es gibt meist genau einen richtigen Weg, etwas zu tun. Das erzeugt konsistenten, lesbaren Code — besonders wichtig in großen Teams.

Kernprinzipien

  • Simplicity first — Wenige Konzepte, kein Overengineering
  • Explizit über implizit — Kein Magic, keine versteckten Kosten
  • Composition over Inheritance — Interfaces statt Klassenhierarchien
  • Concurrency as a first-class citizen — Goroutines & Channels sind eingebaut
  • Fast compile, fast run — Produktivität ohne Kompromisse bei Performance
  • Tooling inklusive — fmt, test, vet, doc sind Teil der Sprache
  • Keine Ausnahmen (exceptions) — Errors sind Werte, kein try/catch

Was Go bewusst weglässt

// Kein Klassen-Keyword
// Keine Vererbung (nur Embedding)
// Keine Exceptions (try/catch)
// Keine Operator-Überladung
// Kein this / self
// Kein null (nur nil)
// Keine optionalen Parameter

// Dafür: strukturelle Einfachheit
// und Lesbarkeit die skaliert.

Bekannte Go-Nutzer: Google, Cloudflare, Docker, Kubernetes, Terraform, Dropbox, Twitch

Go vs. JavaScript / TypeScript — Hauptunterschiede

Go JavaScript TypeScript

Konzeptuelle Unterschiede

Go
Statisch typisiert, kompiliert, kein Runtime nötig
JS
Dynamisch typisiert, interpretiert, V8/Node Runtime
TS
Statische Typen zur Compile-Zeit, wird zu JS kompiliert
Go
Concurrency via Goroutines & Channels (CSP)
JS
Single-threaded, async via Event Loop + Promises
TS
Wie JS — async/await über Promise-Abstraktion
Go
Errors als Rückgabewert — explizit behandeln
JS
try/catch, unhandled rejections möglich
TS
try/catch — Fehlertypen oft unknown
Go
Interfaces strukturell, implizit implementiert
JS
Prototype-basiert, keine Interfaces
TS
Interfaces + Klassen, strukturell typisiert

Syntax-Vergleich: Funktion mit Error-Handling

Go
func fetchUser(id int) (User, error) {
  row, err := db.Query("SELECT …", id)
  if err != nil {
    return User{}, fmt.Errorf("fetchUser: %w", err)
  }
  return row, nil
}
TypeScript
async function fetchUser(id: number): Promise<User> {
  const row = await db.query(`SELECT … WHERE id=$1`, [id])
  return row.rows[0]  // error propagiert als rejected Promise
}

Kernunterschied: Go zwingt dich, jeden Error explizit zu behandeln. In TS/JS können Errors unbemerkt "bubblen" — diszipliniertes Error-Handling ist Konvention, nicht Pflicht.

"Klassen" in Go — Structs, Methods & Interfaces

Struct statt Class

Go hat kein class-Keyword. Stattdessen kombiniert man Struct (Daten) + Methods (Verhalten) + Interface (Vertrag).

// Struct = "Klasse"
type User struct {
  Name  string
  Email string
  age   int  // klein = privat
}

// Konstruktor-Funktion (Konvention)
func NewUser(name, email string) *User {
  return &User{Name: name, Email: email}
}

// Methode auf Pointer-Receiver
func (u *User) Greet() string {
  return "Hi, " + u.Name
}

// Value Receiver (liest nur)
func (u User) IsAdult() bool {
  return u.age >= 18
}

Vererbung → Embedding

Go hat keine Vererbung. Stattdessen: Struct Embedding — ein Struct wird in ein anderes eingebettet und gibt seine Felder & Methoden weiter.

type Animal struct {
  Name string
}

func (a Animal) Speak() string {
  return a.Name + " macht ein Geräusch"
}

// Dog "erbt" Animal via Embedding
type Dog struct {
  Animal        // eingebettet
  Breed string
}

// Methode überschreiben
func (d Dog) Speak() string {
  return d.Name + " bellt"
}

d := Dog{Animal: Animal{Name: "Rex"}}
fmt.Println(d.Speak())  // Rex bellt
fmt.Println(d.Name)     // direkt verfügbar

Interface = Polymorphismus

Interfaces werden implizit implementiert — kein implements. Jeder Typ der die Methoden hat, erfüllt das Interface automatisch.

type Speaker interface {
  Speak() string
}

// Cat implementiert Speaker implizit
type Cat struct { Name string }

func (c Cat) Speak() string { return "Miau" }

// Dog aus oben auch — kein Unterschied!
func MakeNoise(s Speaker) {
  fmt.Println(s.Speak())
}

MakeNoise(Dog{...})  // ✓
MakeNoise(Cat{...})  // ✓

// Vergleich mit TS:
// class Cat implements Speaker {
//   speak() { return "Miau" }  ← explizit
// }

Async & Concurrency — Goroutines, Channels & Context

Goroutines — das Grundprinzip

Eine Goroutine ist ein leichtgewichtiger Thread, der von der Go-Runtime verwaltet wird. Tausende können gleichzeitig laufen. Start-Kosten: ~2 KB Stack (vs. MB bei OS-Threads).

// Goroutine starten — einfach "go"
go doWork()

// Mit WaitGroup synchronisieren
var wg sync.WaitGroup

for i := 0; i < 5; i++ {
  wg.Add(1)
  go func(n int) {
    defer wg.Done()
    fmt.Println("Worker", n)
  }(i)
}

wg.Wait()  // alle Goroutinen abwarten

// Mutex für geteilten State
var mu sync.Mutex
mu.Lock()
counter++
mu.Unlock()

Channels — Kommunikation zwischen Goroutinen

Go's Motto: "Don't communicate by sharing memory — share memory by communicating." Channels sind typisierte, threadsichere Leitungen.

// Unbuffered — blockiert bis gelesen
ch := make(chan int)
go func() { ch <- 42 }()
v := <-ch  // empfangen

// Buffered — blockiert erst wenn voll
jobs := make(chan string, 10)
jobs <- "job1"
jobs <- "job2"
close(jobs)

for j := range jobs {
  fmt.Println(j)
}

// select — wie switch für Channels
select {
case msg := <-ch1:
  fmt.Println(msg)
case ch2 <- "ping":
  fmt.Println("sent")
default:
  fmt.Println("non-blocking")
}

Context — Timeouts & Abbruch

Context trägt Deadlines, Abbruch-Signale und Request-Werte durch die Call-Chain — der Standard-Weg um Goroutinen sauber zu beenden.

// Context mit Timeout
ctx, cancel := context.WithTimeout(
  context.Background(),
  5*time.Second,
)
defer cancel()  // immer defer!

// HTTP-Request mit Context
req, _ := http.NewRequestWithContext(
  ctx, http.MethodGet, url, nil,
)
resp, err := http.DefaultClient.Do(req)

// Goroutine auf ctx.Done() hören
go func() {
  for {
    select {
    case <-ctx.Done():
      return  // sauber beenden
    default:
      doWork()
    }
  }
}()

vs. JS async/await: In Go gibt es kein await. Goroutinen blockieren synchron aus ihrer eigenen Perspektive — die Runtime schedulet sie nicht-blockierend auf OS-Threads. Das Ergebnis ist ähnlich zu async/await, aber ohne Callback-Ketten oder Promise-Chaining.