Go in 2 Weeks #01 | Days 1–2: Go Philosophy & Core Syntax — A C++ Developer’s First Steps

Go in 2 Weeks #01 | Days 1–2: Go Philosophy & Core Syntax — A C++ Developer’s First Steps

이 글의 핵심

Your first steps in Go from a C++ background: installation, := and var, for and range, the GC, go fmt, and modules—always compared with C++ so the mental model clicks.

Series overview

📚 Go in 2 Weeks #01 | Full series index

This article covers Days 1–2 of the two-week Go curriculum for C++ developers.

Previous: Curriculum intro ← | → Next: #02 Memory & data structures


Introduction: meeting Go

Coming from C++, Go can feel almost too minimal: no headers, no template metaprogramming, and no manual new/delete. This article explains Go’s philosophy and core syntax from a C++ perspective. Build tooling elsewhere: CMake, Cargo / Rust, npm modules.

You will learn:

  • Installing Go and the core toolchain (go build, go run, go fmt)
  • Go’s values (simplicity, explicitness, concurrency) vs C++ (pointers, inheritance, generics)—high level
  • Packages and modules (go mod, import, internal)
  • Variables: how := differs from C++ auto
  • Loops: a world with only for
  • Garbage collection
  • go fmt as the style debate ender

Real-world notes

Moving from C++ to Go

After many years of C++ servers, Go felt surprisingly small—until production showed that small is a feature: faster iteration, fewer memory foot-guns, trivial deploys as a single binary.


Go’s philosophy: simplicity, explicitness, concurrency

Go optimizes for readable code and predictable behavior. Rather than many ways to do one thing, it prefers one clear idiom.

PillarMeaningIn practice
SimplicityFewer keywords; tools like go fmt and go test enforce consistencyLess time arguing style or bespoke build scripts in review
ExplicitnessFew implicit conversions; errors are values (if err != nil); concurrency starts with an explicit goFailure modes show up in signatures
ConcurrencyGoroutines + channels as the default story (covered later in the series)Encourages synchronizing by communication

To a C++ developer this can feel “less expressive,” but it usually lowers team-wide maintenance cost.


C++ vs Go: pointers, inheritance, generics

Pointers

  • C++: T*, T&, smart pointers, pointer arithmetic—coexist in one language.
  • Go: pointers are *T, no pointer arithmetic. Nil dereference panics at runtime. Most “ownership” is handled by the GC.
  • Practice: passing addresses to avoid copies is familiar; dangling pointers into freed memory are largely prevented by design.

Inheritance

  • C++: public inheritance, multiple inheritance, virtual for polymorphism.
  • Go: no class inheritance. Use struct embedding and interfaces (method sets) instead (#03, #04).
  • Practice: prefer small interfaces + composition over fat base classes.

Generics

  • C++: templates for compile-time polymorphism—powerful, sometimes painful errors and build times.
  • Go: type parameters since Go 1.18. Not everything must be generic; slices, maps, and channels already feel generic in daily use.
  • Practice: learn interfaces and concrete types first; add generics for repeated algorithms or containers.

Packages and modules

Packages in one minute

  • Every .go file belongs to a package; executables use package main and func main().
  • Files in the same directory share the same package name.
  • Exported names start with a capital letter; unexported names are package-private—think namespace + visibility.

Modules (go mod)

  • A go.mod at the module root fixes the module path (e.g. github.com/org/project) as the import prefix.
  • go mod init, go mod tidy; versions are pinned in go.mod / go.sum.
  • Standard library imports use short paths ("fmt", "net/http").

Typical flow

mkdir myapp && cd myapp
go mod init github.com/you/myapp
go build ./...
go test ./...

internal: packages under myproject/internal/foo cannot be imported from outside the parent tree—useful for narrowing your public API.


Quick reference: functions and control flow

Functions

package example

import "fmt"

// Multiple return values (errors usually last)
func divide(a, b int) (int, error) {
    if b == 0 {
        return 0, fmt.Errorf("division by zero")
    }
    return a / b, nil
}

// Named results (use sparingly with defer)
func stats() (sum int, n int) {
    // ...
    return // naked return — avoid overuse
}

Control

  • Conditions use if, for, switch only (no while).
  • Prefer if err := f(); err != nil { ... } to keep scope tight.

See variables, loops, and conditionals for examples.


Table of contents

  1. Install & Hello World
  2. Go philosophy
  3. C++ vs Go (pointers, inheritance, generics)
  4. Packages & modules
  5. Syntax quick reference
  6. Variables: auto vs :=
  7. Loops: one for for everything
  8. Conditionals
  9. Garbage collection
  10. go fmt
  11. Exercises

1. Install Go and Hello World

Installing Go

# Windows (PowerShell): download .msi from https://go.dev/dl/
go version

# macOS (Homebrew)
brew install go
go version

# Linux: official tarball example
# wget https://go.dev/dl/go1.21.0.linux-amd64.tar.gz
# sudo rm -rf /usr/local/go && sudo tar -C /usr/local -xzf go1.21.0.linux-amd64.tar.gz
# export PATH=$PATH:/usr/local/go/bin

Use go version to verify the toolchain; go env shows GOROOT, GOPATH, GOOS, GOARCH—important for cross-compilation (e.g. GOOS=linux GOARCH=amd64 go build).

Hello World: C++ vs Go

#include <iostream>

int main() {
    std::cout << "Hello, World!" << std::endl;
    return 0;
}
package main

import "fmt"

func main() {
    fmt.Println("Hello, World!")
}

Differences: package main is required; import not #include; func main() has no return type; go build / go run handle deps; static binary by default.

Command-line arguments

#include <iostream>

int main(int argc, char* argv[]) {
    std::cout << "argc: " << argc << '\n';
    for (int i = 0; i < argc; ++i) {
        std::cout << "argv[" << i << "]: " << argv[i] << '\n';
    }
    return 0;
}
package main

import (
    "fmt"
    "os"
)

func main() {
    args := os.Args
    fmt.Println("len(args):", len(args))
    for i, arg := range args {
        fmt.Printf("args[%d]: %s\n", i, arg)
    }
}

Core go commands

TaskC++-ishGo
Buildg++ ...go build
Run./a.outgo run main.go
Formatclang-formatgo fmt
Testexternal frameworksgo test
DepsConan, vcpkg…go mod

2. Variables: auto vs :=

Go favors a small set of rules; C++ offers many styles (auto, constructors, constexpr, …).

Go declarations

var x int = 10
var y = 20
z := 30

const Max = 100

var uninitialized int
fmt.Println(uninitialized) // 0 — zero value, not garbage

a, b, c := 1, 2, 3

var (
    name    string = "Go"
    version int    = 1
)

Zero values

Uninitialized variables get zero values—no undefined behavior like reading uninitialized int in C++.

Short declaration :=

  • Only inside functions
  • At least one name must be new on the left-hand side when mixing with assignment
  • Not allowed at package scope (use var)

var vs :=

SituationPrefer
Inside function, obvious type:=
Package scopevar
Emphasize zero initializationvar
Need explicit typevar

3. Loops: one for for everything

No while or do-while—only for.

Classic for

for i := 0; i < 10; i++ {
    fmt.Println(i)
}

While-style

i := 0
for i < 10 {
    fmt.Println(i)
    i++
}

Infinite

for {
    if shouldStop() {
        break
    }
}

range

nums := []int{1, 2, 3}
for i, v := range nums {
    _, _ = i, v
}
for _, v := range nums { _ = v }
for i := range nums { _ = i }

break / continue / labels

outer:
for i := 0; i < 5; i++ {
    for j := 0; j < 5; j++ {
        if i*j > 10 {
            break outer
        }
    }
}

4. Conditionals

if (no parentheses around the condition)

x := 10
if x > 5 {
    fmt.Println("x is greater than 5")
} else if x > 0 {
    fmt.Println("x is positive")
} else {
    fmt.Println("x is non-positive")
}

// Single-line bodies still need braces
if x > 5 {
    fmt.Println("Greater")
}

// Short declaration limits scope
if v, ok := m[key]; ok {
    fmt.Println("Found:", v)
}

if err := doSomething(); err != nil {
    fmt.Println("Error:", err)
    return
}

// import "os" (and "fmt") in real code
if file, err := os.Open("data.txt"); err != nil {
    panic(err)
} else {
    defer file.Close()
    _ = file
}
C++Go
Condition parensRequiredOmitted
BracesOptional one-linerAlways required
Init + conditionC++17 if-initSupported from the start

switch (no implicit fall-through)

day := 3
switch day {
case 1:
    fmt.Println("Monday")
case 2:
    fmt.Println("Tuesday")
case 3, 4:
    fmt.Println("Mid-week")
default:
    fmt.Println("Other day")
}

// Tagless switch (replaces if-else chains)
x := 15
switch {
case x < 0:
    fmt.Println("Negative")
case x < 10:
    fmt.Println("Small")
case x < 100:
    fmt.Println("Medium")
default:
    fmt.Println("Large")
}

Use fallthrough explicitly when you need C-style fall-through.


5. Garbage collection: beyond new/delete

C++: manual control (performance possible, bugs likely).
Go: GC-managed heap (safety and velocity).

C++ (manual)

int* p = new int(42);
delete p;

int* arr = new int[100];
delete[] arr;

Go (automatic)

p := new(int)
*p = 42

person := &Person{Name: "Alice", Age: 30}
arr := make([]int, 100)
m := make(map[string]int)
m["key"] = 100

Pros: fewer leaks, no dangling pointers from delete, less cognitive load.
Cons: GC pauses (usually small in Go), higher RSS than tuned C++, not ideal for the hardest real-time constraints.

Escape analysis

go build -gcflags="-m" main.go
# Example: "moved to heap", "escapes to heap"
C++Go
Freedelete / RAIIGC
Array deletedelete[]N/A
LeaksCommon foot-gunRare
DanglingPossibleEffectively prevented

6. go fmt: end the style war

C++ style varies wildly (K&R, Allman, GNU, …). Go has one canonical layout—go fmt applies it with no config file.

go fmt main.go
go fmt ./...
gofmt -w main.go
gofmt -d main.go

VS Code (example):

{
  "go.formatTool": "gofmt",
  "editor.formatOnSave": true,
  "[go]": {
    "editor.formatOnSave": true,
    "editor.codeActionsOnSave": {
      "source.organizeImports": true
    }
  }
}

7. Exercises

Exercise 1: FizzBuzz

#include <iostream>

int main() {
    for (int i = 1; i <= 100; i++) {
        if (i % 15 == 0) {
            std::cout << "FizzBuzz\n";
        } else if (i % 3 == 0) {
            std::cout << "Fizz\n";
        } else if (i % 5 == 0) {
            std::cout << "Buzz\n";
        } else {
            std::cout << i << "\n";
        }
    }
    return 0;
}
package main

import "fmt"

func main() {
    for i := 1; i <= 100; i++ {
        switch {
        case i%15 == 0:
            fmt.Println("FizzBuzz")
        case i%3 == 0:
            fmt.Println("Fizz")
        case i%5 == 0:
            fmt.Println("Buzz")
        default:
            fmt.Println(i)
        }
    }
}

Exercise 2: multiplication table

package main

import "fmt"

func main() {
    for i := 2; i <= 9; i++ {
        for j := 1; j <= 9; j++ {
            fmt.Printf("%d x %d = %d\n", i, j, i*j)
        }
        fmt.Println()
    }
}

Exercise 3: slice sum

#include <iostream>
#include <vector>

int sum(const std::vector<int>& numbers) {
    int total = 0;
    for (int num : numbers) {
        total += num;
    }
    return total;
}
package main

import "fmt"

func sum(numbers []int) int {
    total := 0
    for _, num := range numbers {
        total += num
    }
    return total
}

Exercise 4: filter even numbers

std::vector<int> filterEven(const std::vector<int>& numbers) {
    std::vector<int> result;
    result.reserve(numbers.size());
    for (int num : numbers) {
        if (num % 2 == 0) {
            result.push_back(num);
        }
    }
    return result;
}
func filterEven(numbers []int) []int {
    result := make([]int, 0, len(numbers))
    for _, num := range numbers {
        if num%2 == 0 {
            result = append(result, num)
        }
    }
    return result
}

Exercise 5: word frequency

package main

import "fmt"

func main() {
    words := []string{"apple", "banana", "apple", "cherry", "banana", "apple"}
    freq := make(map[string]int)
    for _, word := range words {
        freq[word]++
    }
    for word, count := range freq {
        fmt.Printf("%s: %d\n", word, count)
    }
}

8. Days 1–2 checklist

  • Install Go; go version works
  • Hello World with go run / go build
  • Understand := vs var and zero values
  • Use for for every loop style + range
  • if with short declaration; switch without manual break
  • Run go fmt ./...
  • Understand go mod, import paths, exported names
  • Complete the exercises

9. What’s next

#02 covers pointers (no arithmetic), slices, maps, and structs—in depth.


📚 Series navigation

PreviousIndexNext
← Curriculum📑 Index#02 Data structures →

Go in 2 weeks: Curriculum#01#02#03#04#05#06#07#08#09


TL;DR: Go trims C++ complexity; master :=, for, and go fmt and you’ve finished the first milestone.

  • Go series index
  • Two-week Go curriculum
  • C++ developer’s view of Go
  • C++ vs Go

Keywords

Go tutorial, Golang install, short variable declaration, Go for loop, range, garbage collector, go fmt, Go vs C++, golang basics.

Practical tips

  • Fix compiler warnings first; reproduce bugs with minimal examples.
  • Profile before optimizing.

FAQ

Q. When do I use this in production?

A. Day-to-day: declaring variables, looping, formatting with go fmt, and trusting the GC—exactly the baseline for services and CLIs.

Q. Prerequisites?

A. Follow the curriculum from the intro or the C++ series index.

Q. Go deeper?

A. go.dev/doc and cppreference for C++ parallels.