1-1. Hello World - Go の世界に入る

所要時間: 30-50分(がっつりなら2セッション分) コミット内容: ~/learn/go/day01/hello/ 一式(main.go と go.mod)


このレッスンのゴール(できるようになること)

  • go mod init で新規 Go プロジェクトを作れる
  • package main / func main() の意味を説明できる
  • go rungo build の違いを使い分けられる
  • fmt.Printlnfmt.Printf を使い分けられる
  • 大文字始まり/小文字始まりの可視性ルールが言える

なぜ学ぶか - 実務での悩みと結びつける

docker や kubectl のソースを読みたい時、Go が読めないと詰む」「Node でビルドしてランタイム配って…が面倒」「API サーバーで goroutine を使った並行処理を書きたい」 - これらが本日のスタート地点。Hello World は単なる出力ではなく、Go の世界観を1ファイルに凝縮した最小モデル だ。package main、import、関数定義、可視性、ビルドコマンド - ここで触る要素はこの先の全章で再登場する。

前章とのつながり

これが最初のレッスン。前提知識として JS/TS の関数構文・モジュールシステムを知っている ことだけを期待する。それ以外の Go 特有の文法は本章でゼロから入る。

これができると何が嬉しいか

  • GitHub の Go プロジェクトをクローンして go run で動かせる → OSS の挙動を読みながら確認できる
  • 「Go で何か作ってみて」と言われた時に手が動く → 0分でプロジェクト初期化、5分で動くバイナリ
  • Docker イメージに置くシングルバイナリ が手に入る → デプロイの土台が出来る

大前提: なぜ今 Go を学ぶか

JS/TS でフロントエンドをやっていたあなたが、バックエンドで一番最初に Go に触れる意味を整理する。

  • Docker、Kubernetes、Terraform、Hugo、Prometheus … 現代インフラの主要 OSS はほぼ Go 製。サーバーサイドを「使う側」から「読み書きする側」に踏み込むなら、Go の文法は避けて通れない
  • コンパイル型・静的型付け: JS のように「実行してみないと型エラーが分からない」が無い。ビルドが通ればまずまず動く、という安心感は TypeScript 経験者には馴染みやすい
  • 並行処理 (goroutine / channel): API サーバーで「100リクエスト同時にDBに問い合わせ」みたいな処理が、Node.js より圧倒的に書きやすい
  • デプロイが楽: ビルドすると1枚のバイナリが出来る。Docker イメージのサイズも小さい。Python のように仮想環境やランタイムを別途配る必要がない

Go の生まれと哲学

Google の Rob Pike / Ken Thompson / Robert Griesemer が 2007年に設計開始、2009年に公開。

設計思想

  • Less is more: 言語仕様を意図的に小さく保つ。「機能Aと機能Bがあるけどどっち使う?」みたいな迷いを排除
  • 明示的 (explicit): 隠れた挙動を嫌う。例外も throw/catch じゃなく「エラーは戻り値」
  • コンパイルが速い: C++ のビルドが遅すぎたという反省から、秒速ビルドを最初から狙った

JS/TS 界の流儀(短い記述・暗黙の型変換・チェーン文化)とは正反対の思想。最初は窮屈に感じるが、それが Go


セッション①: Go をインストールして最初のプログラム(25-30分)

0. 録画スタート&作業ディレクトリ

mkdir -p ~/log ~/learn/go/day01
cd ~/learn/go/day01
script ~/log/go_day01.log

1. Go のインストール確認

# バージョン確認
go version
# 例: go version go1.23.4 darwin/arm64

go コマンドの本質

Go は言語名であると同時に、ツールチェイン全部入りgo という単一コマンドを提供する。

  • go run : ソースを直接実行(裏でビルドして実行、成果物は捨てる)
  • go build : バイナリを作る
  • go mod : 依存管理
  • go test : テスト実行
  • go fmt : フォーマット
  • go vet : 静的解析

JS/TS で言うと: node + npm + tsc + eslint + prettier + jestgo 一発に統合されているイメージ。最初に覚えるコマンドは go 1つでいい。

Go が入っていない場合

# Homebrew で入れる
brew install go
 
# 確認
go version

macOS の場合 ~/go/binPATH に通っていないとビルドしたバイナリが見つからない。Homebrew インストール時に ~/.zshrc への追記を案内してくれるので従う。

2. Hello World を書く前に: モジュールを初期化する

# プロジェクト用ディレクトリ
mkdir -p ~/learn/go/day01/hello
cd ~/learn/go/day01/hello
 
# モジュール初期化
go mod init example.com/hello

実行後、カレントに go.mod というファイルが出来る。中身は3行ほど。

module example.com/hello
 
go 1.23

go mod init とは何か

プロジェクトを Go モジュール として宣言するコマンド。go.mod という設定ファイルを生成する。

go.mod の役割

  • このプロジェクトの「名前 (module path)」を決める
  • 使う Go のバージョンを宣言する
  • 後で依存ライブラリを追加すると、ここに記録される

JS/TS で言うと: npm init -ypackage.json を作るのと完全に同じ役割。go.mod = package.json

モジュール名の付け方

example.com/hello のように 「ドメイン名 + プロジェクト名」のような形 で書く慣習がある。

理由: 後で公開する時に、そのリポジトリの URL がそのままモジュール名になるから。

  • 公開しない学習用: example.com/hello でも myapp でも何でもいい
  • GitHub にあげる予定: github.com/<ユーザー名>/<リポジトリ名> にする(例: github.com/kuroiwa/myapp

落とし穴: 後でモジュール名を変えると import 文を全部書き直すハメになる。最初に決めておく。

go mod init を忘れるとどうなるか

モジュール外で go run すると最近の Go では大体エラーになる:

go: go.mod file not found in current directory or any parent directory

「ここどのプロジェクト?」が分からないと Go は動かない。新規プロジェクトは必ず go mod init から

3. main.go を書く

hello.go という名前で以下を保存(エディタは VS Code / Cursor / vim 何でも)。

package main
 
import "fmt"
 
func main() {
	fmt.Println("Hello, Go!")
}

これで保存して、

go run hello.go
# Hello, Go!

出ました。

package main の意味

Go ではすべての .go ファイルが何かのパッケージに属する。1行目で「このファイルは main パッケージの一部」と宣言している。

特別なパッケージ main

  • main という名前のパッケージは「実行可能プログラム」として扱われる
  • その中に func main() を1つだけ書く → これがエントリポイント
  • 他のパッケージ名(utils, db など)にすると「ライブラリ」になり、func main() を書けない

JS/TS で言うと: main.tsmain() 関数が自動で呼ばれるようなもの。package main は「これは実行ファイルだよ」というマーカー。

import "fmt" の意味

fmtformat の略で、標準ライブラリの中で「フォーマットされた I/O」を担当するパッケージ。Println Printf Sprintf などが入っている。

使い所

  • fmt.Println : 改行つきの出力。デバッグ・学習用に最頻出
  • fmt.Printf : %s %d %v などのフォーマット指定子で整形
  • fmt.Sprintf : 結果を文字列として受け取る(ファイル名生成など)

JS で言うと: console.logfmt.Println 相当。ただし fmt は文字列整形まで担う。

func main() { ... } の構造

  • func : 関数を定義するキーワード(JS の function 相当)
  • main : 関数名
  • () : 引数(main は引数なし)
  • { ... } : 関数本体

Go では 波カッコ { が関数名と同じ行に必須。改行して { を次の行に置くとコンパイルエラーになる。

// NG: コンパイルエラー
func main()
{
    fmt.Println("hi")
}

JS のように好きに改行できると思ってると最初の落とし穴。フォーマットは go fmt が全部直してくれるので、慣れる前に保存時 go fmt を癖にする。

セミコロンとカッコの作法

  • 行末セミコロン ; 不要(書いてもいいが書かないのが普通)
  • if / for の条件式にカッコ ( ) 不要(書くと逆にエラー)
  • 波カッコ { } は省略不可(1文だけの if でも省略できない)

JS から来ると「カッコは書く、セミコロンは付ける」が体に染みついているので、最初は違和感を覚える。

4. go run で実行

go run hello.go

go run の本質

ソースコードを受け取って、一時ディレクトリでビルド → そのバイナリを実行 → 終わったらバイナリ破棄 をワンライナーで行うコマンド。

使い所

  • 動作確認・デバッグ中の手早い実行
  • スクリプト的に1回だけ動かしたい時

落とし穴: 起動は速く感じるが、毎回内部でビルドしているので繰り返し実行するなら go build でバイナリを作ってそれを叩いた方が速い

複数ファイルの実行

ファイルが増えたら go run hello.go util.go のように並べる、または同じディレクトリの *.go をまとめて指定する:

go run .

. はカレントディレクトリの main パッケージを実行という意味。実用的にはこれが一番楽。

5. go build でバイナリを作る

go build
# → hello という実行ファイルができる(モジュール名末尾が hello だから)
 
./hello
# Hello, Go!
 
# 別名で作りたい
go build -o myapp
./myapp

go build の本質

ソースをコンパイルして 実行可能なバイナリファイル(マシン語) を作る。一度作れば Go ランタイムが入っていない PC でも動く(シングルバイナリ)。

使い所

  • 配布: 同僚や本番サーバーに「これ実行して」と渡す
  • Docker イメージ作成: ビルドしたバイナリだけを入れた軽量イメージが作れる
  • 性能計測: 一時ビルドの go run より起動が速い

クロスコンパイル: GOOS=linux GOARCH=amd64 go build でMac上からLinux用バイナリも作れる。Goのキラー機能の1つ。

バイナリのサイズに最初驚く

シンプルな Hello World でも生成バイナリは 1.5MB~2MB くらいある。これは Go ランタイムやガベージコレクタを内包しているから。

小さくしたいとき:

go build -ldflags="-s -w" -o myapp

デバッグ情報を削るので20-30%小さくなる。さらに upx で圧縮も可能(が、本番では普通やらない)。


セッション②: JS/TS との違いを整理(25-30分)

6. JS/TS 経験者向け対応表

// Go
package main
 
import "fmt"
 
func add(a int, b int) int {
	return a + b
}
 
func main() {
	result := add(1, 2)
	fmt.Println(result)
}
// TypeScript
function add(a: number, b: number): number {
	return a + b;
}
 
const result = add(1, 2);
console.log(result);

似ているところ / 違うところ

観点TypeScriptGo
実行tsc で JS にトランスパイル → node 実行go buildネイティブバイナリ
静的(実行時には消える)静的(実行時も型がある)
型注釈の位置変数・引数の 後ろ (a: number)変数・引数の 後ろ (a int) - 似てる
関数定義function add(a: number): numberfunc add(a int) int
文字列補間`Hello, ${name}`fmt.Sprintf("Hello, %s", name)
null/undefined2種類あるnil のみ(次回詳しく)
エラー処理try/catch戻り値で返す(result, err := ...
クラスあり無い(struct + メソッド)
パッケージ管理npm / pnpmgo mod
フォーマッタPrettier(任意)gofmt標準・強制

一番のカルチャーショック: クラスが無い、例外が無い、ジェネリクスは長いこと無かった(1.18 で導入)、三項演算子も無い。意図的に機能が削られている

7. 命名規則: 大文字 = 公開、小文字 = 非公開

package mypkg
 
// 公開: 他のパッケージから使える
func PublicFunction() {
	// ...
}
 
// 非公開: このパッケージ内でのみ使える
func privateFunction() {
	// ...
}
 
// 公開フィールド
type User struct {
	Name string  // 公開
	age  int     // 非公開
}

Go の「アクセス制御」は識別子の最初の文字で決まる

JS/TS の public private export のようなキーワードは 無い。代わりに:

  • 大文字始まり (User, PrintAll) = public (exported)
  • 小文字始まり (user, printAll) = package-private (unexported)

同じパッケージ内なら大文字小文字に関係なくアクセスできる。他パッケージから見ると小文字始まりは存在しないも同然

使い所

  • 内部用ヘルパー関数は小文字で始める → うっかり外部に漏らさない
  • API として公開したい型・関数は大文字で始める

落とし穴: TypeScript のように export を書き忘れたから動かないんじゃなくて、変数名の頭文字を間違えただけで「未定義」エラー になる。デバッグ時に「あれ、関数があるのに見えない」となったら大体これ。

8. go fmt でフォーマット統一

# 現在のディレクトリのファイルをすべてフォーマット
go fmt ./...
 
# 表示だけして書き換えない
gofmt -l hello.go

Go の公式フォーマッタは「議論の余地なし」の文化

Go コミュニティでは インデント・スペース・改行の流儀で議論しない。理由: gofmt が全部決めてしまうから。

  • インデント: タブ
  • 1行の最大長: 制限なし(推奨はあるが強制ではない)
  • import 並び順: 自動整列

使い所

  • 保存時に自動 fmt(VS Code/Cursor の Go 拡張で「format on save」をONに)
  • CI で gofmt -d . を走らせて差分があれば失敗にする

落とし穴: 個人の好みで「ここはスペース入れたい」と思っても、gofmt が容赦なく消す。抵抗せず受け入れるのがGoスタイル。

9. 自作チャレンジ

// hello.go を以下のように書き換えて、go run . で動かしてみる
package main
 
import "fmt"
 
func main() {
	name := "Takato"
	fmt.Printf("Hello, %s! Welcome to Go.\n", name)
 
	// 計算もしてみる
	sum := 1 + 2 + 3
	fmt.Printf("1+2+3 = %d\n", sum)
}

実行:

go run .
# Hello, Takato! Welcome to Go.
# 1+2+3 = 6

Printf のフォーマット指定子(よく使う)

指定子用途
%s文字列
%d整数
%f浮動小数(%.2f で小数点2桁)
%v何でも表示(迷ったらこれ)
%+v構造体をフィールド名付きで表示(デバッグ用)
%T型を表示(型確認デバッグに便利)
\n改行

練習課題

# 1. 新規プロジェクトを作る
mkdir -p ~/learn/go/day01/greeting
cd ~/learn/go/day01/greeting
go mod init example.com/greeting
 
# 2. main.go を作って、自分の名前と今日の日付を表示する
# (ヒント: import "time" して time.Now() を使う)
 
# 3. go run . で動作確認
 
# 4. go build -o greet でバイナリを作る
 
# 5. ./greet で実行
 
# 6. go.mod の中身を cat で確認
cat go.mod

動いたら次に進める

  • go.mod が出来ている
  • go run . で何か出力される
  • go build で出来たバイナリを ./バイナリ名 で実行できる

ここまでで「Go プロジェクトの骨格」を体に入れた状態になる。


締め: 振り返り(10分)

1. セッション録画を終了

exit

2. 今日の発見(このノートに追記)

- 一番「TS と違うな」と思ったところ:
  現時点ではそこまで差は感じない
  ただ、ビルドするとランタイム環境がなくても動くのは便利だなと思った
  攻撃者の側にたつと攻撃用のプログラムを知らない人の環境でも実行しやすそう
- ハマったエラー(あれば):
  今のところ特にないが、`Printf` のフォーマット指定子がだるい。。
- go fmt で勝手に直された箇所:
  なし
- 明日やりたいこと:
  がんばっておぼえていきたい

メンターコメント (2026-05-16)

  • TS との差「現時点では感じない」: Day 1 では正解の感覚。構文は本当に似てる。違いが効くのは Day 5-6 の スライス/マップの参照渡しの罠、Day 4 の 多値返却とエラー処理、Level 2 の interface と並行処理。「あ、こりゃ別物だ」と腑に落ちる瞬間が必ず来るのでお楽しみに
  • 「攻撃側に立つと配布しやすい」: 自分でそこまで言語化できてるのは強い。Week 7 のセキュリティ章で「Go製マルウェアの増加」の話と繋がる。Day 1 の気付きが Week 7 で点が線になるはず
  • 「Printf のフォーマット指定子がだるい」: 同感。実は fmt.Println で十分なケースは多い。Printf は「複数値を整形したい時の最終手段」。本格運用では log/slog の Info/Debug など名前付きキー値で書く方が主流(Level 3 で扱う)。だるさは正しい感覚
  • 「go fmt で直された箇所: なし」← 事実誤認: 実際は3箇所直ってる。git diff HEAD~ HEAD -- day01/greeting/main.go で振り返り推奨
    • name:= "Takato"name := "Takato":= 前のスペース)
    • now :=time.Now()now := time.Now():= 後のスペース)
    • fmt.Print(name , now)fmt.Print(name, now), 前の不要スペース削除) gofmt は「自分が直した」じゃなく「Go側が黙って直す」なので、気付かないのは普通。エディタの保存時自動整形 を仕込めば一生意識しなくていい
  • 「明日やりたいこと: がんばって覚えていきたい」: 抽象目標は ADHD の天敵。実行されない。具体化推奨:
    • 例: 「Linux Day 2 を 25分」
    • 例: 「Go Day 2 を始めて、変数・型・定数の3つだけ触る」
    • 例: 「gofmt をエディタ保存時に走らせる設定を入れる」 3 つ並べたら 1 つだけやれば勝ち、というルールで

自己評価チェックリスト

手を動かせた(実装)

  • go version で Go が入っていることを確認した
  • go mod initgo.mod を作った
  • package mainfunc main() を書いて Hello World を出した
  • go run . で実行した
  • go build でバイナリを作って ./バイナリ名 で実行した

説明できる(メタ認知)

  • 大文字始まり/小文字始まりの違いを人に説明できる
  • fmt.Printlnfmt.Printf の違いを説明できる
  • go rungo build の使い分けが言える
  • package main が「実行ファイル」のマーカーだと説明できる

やらかし回避(アンチパターン)

  • go mod init 忘れエラーを再現できる
  • { を改行して書いてコンパイルエラーを見た
  • エディタの保存時 gofmt 設定を確認した

詰まった時のチートシート

やりたいことコマンド
バージョン確認go version
プロジェクト初期化go mod init <モジュール名>
実行(カレントの main)go run .
実行(特定ファイル)go run hello.go
バイナリ作成go build
バイナリ作成(名前指定)go build -o myapp
フォーマットgo fmt ./...
静的解析go vet ./...
依存追加go get <パス>
標準出力fmt.Println(...)
整形出力fmt.Printf("%s %d\n", s, n)

「実務OK」基準

このレッスンで身に付くべき感覚:

  • 新規 Go プロジェクトの開始手順が手に染みている: mkdir → go mod init → main.go の流れに迷わない
  • go rungo build の使い分けが言える: 動作確認は go run、配布は go build
  • fmt.Printlnfmt.Printf を使い分けられる: ベタ出力か整形出力か
  • 大文字/小文字の意味が分かる: 公開・非公開はキーワードじゃなく頭文字
  • go.modpackage.json の親戚だと言える

ここまで来たら「Go の入口の入口」をくぐった状態。次は変数と型に進む。


次のレッスン

1-2. 変数と型 へ。

var:= の違い、基本型、定数 constiota、そして JS の null/undefined との違いを掘る。