← コース一覧
Go
Googleが開発した、シンプルさと並行処理に強みを持つコンパイル言語です。基本文法からスライス・マップ・構造体、インターフェース、goroutineとチャネルによる並行処理、ジェネリクス、標準ライブラリの実践までを1テーマずつ順番に学びます。コードはその場で実行できます。
公式ドキュメント: Go Documentation ↗導入とツール
- 1go コマンド — run・build・fmt・test・modGo には言語本体とともに go という単一のコマンドラインツールが付いてきます。ビルド・実行・整形・テスト・依存管理といった開発に必要な作業のほとんどが、この go コマンドのサブコマンドだけで完結します。別途ビルドツールを導入しなくても開発が始められるのが Go の大きな特徴です。
- 2実行可Hello, World — package main と func mainGo のプログラムは package 宣言から始まります。実行可能なプログラムを作るときは、必ず先頭に package main と書きます。そして main パッケージの中に func main() という関数を用意すると、プログラムを起動したときにそこが入り口(エントリーポイント)として呼ばれます。
- 3実行可パッケージと import — 大文字で始まる名前が公開されるGo のコードはすべて何らかのパッケージに属します。ファイルの先頭の package 宣言でそのファイルが属するパッケージを決め、別のパッケージの機能を使いたいときは import でそのパッケージを取り込みます。標準ライブラリには fmt(入出力)、strings(文字列操作)、math(数学)など多くのパッケージが用意されています。
- 4Go モジュール — go.mod による依存管理現在の Go ではプロジェクトを「モジュール」という単位で管理します。プロジェクトのルートで go mod init モジュールパス と実行すると go.mod というファイルが作られ、ここにモジュール名・必要な Go のバージョン・依存するパッケージとそのバージョンが記録されます。モジュールパスには通常 github.com/ユーザー名/リポジトリ名 のような、配布先を表す文字列を使います。
- 5gofmt と標準ツール — 整形と静的検査の思想Go にはコードの見た目を統一するための公式整形ツール gofmt が標準で付属します(go fmt はこれを呼び出すラッパーです)。インデントや空白の入れ方、波かっこの位置といったスタイルは gofmt が機械的に決めるため、Go のコードはどのプロジェクトでもほぼ同じ見た目になります。スタイルを議論する必要がない、というのが Go の重要な思想です。
- 6実行可変数宣言 — var と := の違いGo で変数を作る方法は主に二つあります。一つは var キーワードを使う書き方で、var x int = 5 のように型と初期値を書きます。型は右辺から推論できるので var x = 5 と省略でき、初期値を書かなければ var x int のようにゼロ値(int なら 0)で初期化されます。var はパッケージのトップレベルでも関数の中でも使えます。
基本文法
- 7実行可基本型とゼロ値 — int・float64・bool・stringGo の基本型には、整数の int、浮動小数点数の float64、真偽値の bool、文字列の string などがあります。整数には int8 から int64、符号なしの uint 系など細かい種類もありますが、特に理由がなければ int を使えば十分です。小数を扱うときは float64 が標準的な選択になります。
- 8実行可定数と iota — const による連番の定義変わらない値には const を使って定数を定義します。const Pi = 3.14 のように書くと、その値を後から書き換えることはできません。定数はコンパイル時に決まる値である必要があり、文字・文字列・数値・真偽値などを表せます。型を書かない定数は使われる文脈に応じて柔軟に扱われます。
- 9実行可演算子と型変換 — 明示的な変換が必要Go には四則演算の + - * /、剰余の %、比較の == や < >、論理演算の && || ! など、一般的な演算子がそろっています。整数同士の割り算は結果も整数になり小数点以下が切り捨てられる、という点は多くの言語と同じです。
- 10実行可文字列と rune — UTF-8 とバイト・文字の違いGo の string は UTF-8 でエンコードされたバイト列です。len(s) は文字列の「バイト数」を返すので、ASCII 文字だけなら文字数と一致しますが、日本語のようなマルチバイト文字を含むと文字数とは異なります。たとえば「あ」は UTF-8 で 3 バイトを占めるため、文字列「あ」の len は 3 になります。
- 11実行可if 文 — 条件分岐と初期化付き if条件分岐は if で書きます。Go では条件式をかっこで囲む必要がなく、if x > 0 { ... } のように書きます。一方で、続くブロックの波かっこ { } は省略できません。else や else if も他の言語と同様に使えます。条件式は必ず bool 型でなければならず、数値を真偽値の代わりに使うことはできません。
- 12実行可for 文 — Go 唯一のループGo のループ構文は for だけです。while や do-while といったキーワードはなく、for ひとつで全部のループを表現します。基本の形は for 初期化; 条件; 後処理 { ... } で、初期化文・条件式・各反復後の処理をセミコロンで区切って書きます。条件式をかっこで囲まない点は if と同じです。
- 13実行可for range — 配列・スライス・マップ・文字列を回すfor に range を組み合わせると、コレクションの要素を順番に取り出して繰り返せます。スライスや配列では、各反復でインデックスと値の 2 つが得られます。インデックスが不要なときは、アンダースコア _ を使って for _, v := range nums のように値だけを受け取れます。
- 14実行可switch 文 — fallthrough なしと条件式 switchswitch は値による分岐を読みやすく書くための構文です。Go の switch は他の多くの言語と違い、各 case の最後で自動的に switch を抜けます。break を書く必要はありません。意図的に次の case へ処理を続けたいときだけ、明示的に fallthrough と書きます。これにより break の書き忘れによるバグが起きません。
- 15実行可関数の基本 — 定義・引数・戻り値関数は func キーワードで定義します。func 関数名(引数名 型, ...) 戻り値の型 { ... } という形で、引数には名前のあとに型を書きます。Go では型を変数名の後ろに書くのが特徴で、func add(a int, b int) int のようになります。同じ型の引数が続くときは func add(a, b int) int とまとめて書けます。
- 16実行可複数戻り値 — (値, error) パターンの導入Go の関数は複数の値を同時に返せます。戻り値の型を func divide(a, b int) (int, error) のようにかっこで囲んで並べ、return 結果, エラー のようにカンマ区切りで返します。呼び出し側も result, err := divide(...) のように複数の変数で受け取ります。
- 17実行可名前付き戻り値 — 戻り値に名前を付けるGo では戻り値にも名前を付けられます。func split(sum int) (x, y int) のように、戻り値の型と一緒に名前を書くと、その名前が関数内であらかじめ宣言された変数として使えます。これらの変数はそれぞれの型のゼロ値で初期化された状態で始まります。
- 18実行可コメントとドキュメンテーション — godoc の慣習Go のコメントは // で行末まで、または /* ... */ で複数行を書けます。書き方自体は一般的ですが、Go にはコメントをそのままドキュメントとして活用する強い慣習があります。公開する関数や型のすぐ上に書いたコメントは、ドキュメント生成ツールに拾われ、そのままその要素の説明として表示されます。
複合型
- 19実行可配列 — 固定長の並び配列は同じ型の要素を決まった個数だけ並べたものです。Go では [3]int のように、要素数を型の一部として書きます。つまり [3]int と [4]int は別の型で、要素数はあとから変えられません。要素にはインデックス(0 始まり)でアクセスし、宣言時に値を書かなければ、各要素はその型のゼロ値(int なら 0)で初期化されます。
- 20実行可スライス — 可変長の並び(len と cap)スライスは、土台となる配列の一部分を指し示す軽量なビューです。要素数を型に含めないので [int] ではなく []int と書き、配列と違って長さを動的に扱えます。a[low:high] のように既存の配列やスライスを切り出して作ると、low から high-1 までの要素を共有して参照します。
- 21実行可append とスライスの内部 — 土台配列の共有スライスに要素を足すには組み込み関数 append を使います。append(s, 値...) は新しいスライスを返すので、s = append(s, x) のように結果を代入し直すのが基本形です。容量(cap)に空きがあれば同じ土台配列に書き込み、空きがなければより大きな配列を新しく確保して全要素をコピーしてから追加します。
- 22実行可make と copy — 長さ・容量を指定して作るあらかじめ長さや容量が分かっているときは、組み込み関数 make でスライスを作れます。make([]int, length) は length 個のゼロ値で埋めたスライスを、make([]int, length, capacity) は容量も指定して作ります。最初から十分な容量を確保しておくと、append のたびに土台配列を作り直す無駄が減り、効率が良くなります。
- 23実行可マップ — キーと値の連想配列マップはキーから値を引くための連想配列で、map[キーの型]値の型 と書きます。使う前に make(map[string]int) で初期化するか、リテラル map[string]int{"a": 1} で作ります。m["key"] = 値 で代入、m["key"] で取得、delete(m, "key") で削除します。存在しないキーを引くと、エラーにはならず値の型のゼロ値が返ります。
- 24実行可構造体 — フィールドをまとめた型構造体(struct)は、関連する値を名前付きのフィールドとして一つにまとめた型です。type Point struct { X int; Y int } のように type で名前を付けて定義します。フィールドには p.X のようにドットでアクセスし、各フィールドは初期化しなければその型のゼロ値になります。
- 25実行可構造体の埋め込み — 継承ではなくコンポジションGo にはクラスの継承がありません。代わりに、構造体の中に別の型をフィールド名なしで書く「埋め込み(embedding)」で機能を組み合わせます。type Dog struct { Animal; Name string } のように書くと、Dog は Animal を内包し、Animal のフィールドやメソッドを d.Name のように自分のものであるかのように直接呼び出せます。これをフィールドの昇格(promotion)と呼びます。
- 26実行可ポインタ — & と *、なぜ使うのかポインタは値そのものではなく、値が置かれているメモリ上の場所(アドレス)を指す値です。&x で変数 x のアドレスを取り出し、その型は *int(int へのポインタ)になります。逆に *p と書くと、ポインタ p の指す先の値を読み書きできます。この *p を通じた操作を「デリファレンス(参照外し)」と呼びます。
- 27実行可new と複合リテラル — 値を作る二つの方法Go で新しい値を作る方法は大きく二つあります。一つは複合リテラルで、Point{X: 1, Y: 2} のように型名と中括弧で値を直接記述します。&Point{X: 1, Y: 2} と先頭に & を付ければ、その値へのポインタが得られます。構造体やスライス、マップを作るときはこの複合リテラルが最もよく使われます。
関数とメソッド
- 28実行可可変長引数 — ... で個数を自由に引数の型の前に ... を付けると、その関数は同じ型の引数を何個でも受け取れる可変長引数になります。func sum(nums ...int) のように書くと、呼び出し側は sum(1, 2, 3) のように好きな個数を渡せます。関数の中では nums は []int 型のスライスとして扱えるので、range で回したり len で個数を数えたりできます。
- 29実行可クロージャ — 状態を閉じ込めた関数Go の関数は他の関数の中で定義でき、その内側の関数は外側の変数を捕捉して使い続けられます。このように周囲の変数を取り込んだ関数をクロージャと呼びます。関数を戻り値として返すと、返された関数はその外側の変数を参照したまま生き続け、まるで自分専用の状態を持っているかのように振る舞います。
- 30実行可defer — 後始末を予約する(LIFO)defer を付けた関数呼び出しは、その場では実行されず、現在の関数が return するか終了する直前にまとめて実行されます。ファイルを開いたらすぐ defer f.Close() と書いておけば、どの経路で関数を抜けても確実に閉じられる、というように「後始末を忘れない」ために使います。処理本体と後始末を近くに書けるので読みやすくなります。
- 31実行可panic と recover — 異常時の脱出と復帰panic は、これ以上処理を続けられない異常事態を知らせる仕組みです。panic が起きると現在の関数の実行は止まり、登録済みの defer を実行しながら呼び出し元へさかのぼって伝播し、最後まで止められなければプログラムが異常終了します。配列の範囲外アクセスや nil ポインタのデリファレンスなど、実行時エラーも panic として発生します。
- 32実行可メソッド — レシーバを持つ関数メソッドは、特定の型に結び付いた関数です。func (p Point) Distance() float64 のように、関数名の前に「レシーバ」と呼ばれる特別な引数を一つ書きます。このレシーバが、そのメソッドがどの型に属するかを表します。呼び出すときは p.Distance() のように、値にドットでつなげて使います。
- 33実行可値レシーバとポインタレシーバの違いレシーバには値レシーバ (p Point) とポインタレシーバ (p *Point) の二種類があります。値レシーバはメソッド呼び出し時にレシーバがコピーされるため、メソッドの中でフィールドを書き換えても元の値には反映されません。一方ポインタレシーバは元の値を指すので、フィールドを書き換えるとそのまま呼び出し元に反映されます。
- 34実行可メソッドセット — レシーバの選び方型がどんなメソッドを持つかの集合を「メソッドセット」と呼びます。ここにはルールがあります。値型 T のメソッドセットには値レシーバのメソッドだけが含まれ、ポインタ型 *T のメソッドセットには値レシーバとポインタレシーバの両方が含まれます。つまりポインタの方がより多くのメソッドを持ちます。
- 35実行可第一級関数 — 関数を値として扱うGo では関数も値の一種(第一級オブジェクト)です。変数に代入したり、引数として他の関数に渡したり、戻り値として返したりできます。関数の型は func(int) int のように引数と戻り値の型で表され、この型を持つ変数に関数を入れて、後から呼び出せます。
- 36実行可再帰 — 自分自身を呼ぶ関数再帰とは、関数が自分自身を呼び出して問題を解く手法です。階乗やフィボナッチ数、木構造のたどりなど、「同じ形のより小さな問題」に分解できる処理を素直に表現できます。再帰には必ず、それ以上分解しない「基底ケース(終了条件)」と、問題を小さくして自分を呼ぶ「再帰ケース」が必要です。
インターフェースと抽象化
- 37実行可インターフェース — 暗黙の実装インターフェースは「どんなメソッドを持っているか」だけを定める型です。type Shape interface { Area() float64 } のように、必要なメソッドのシグネチャを並べて宣言します。ある型がそのメソッドをすべて備えていれば、その型は自動的にそのインターフェースを満たします。
- 38実行可インターフェース値 — 動的型と動的値インターフェース型の変数は、内部に「動的型(実際に入っている具体的な型)」と「動的値(その値そのもの)」のペアを保持しています。var s Shape = Circle{R: 2} と書くと、s の動的型は Circle、動的値はその Circle の値、という二つの情報を持ちます。%T と %v で表示すると、この型と値の両方を確認できます。
- 39実行可空インターフェース interface{} と anyメソッドを一つも要求しないインターフェースを空インターフェースと呼び、interface{} と書きます。どんな型もメソッドゼロ個という条件は満たすので、空インターフェースの変数にはあらゆる型の値を入れられます。Go 1.18 以降では、これと完全に同じ意味の別名 any が導入され、読みやすさのため any を使うのが推奨されています。
- 40実行可型アサーション — 具体的な型を取り出す型アサーションは、インターフェース値の中身が特定の具体型であることを確かめ、その型の値として取り出す操作です。v := i.(string) と書くと、i の動的型が string ならその値が取り出せます。もし違う型だった場合、この一値だけ受け取る形では実行時パニックになります。
- 41実行可型switch — 型ごとに分岐する調べたい型が複数あるときは、型 switch を使うと型アサーションを並べるより簡潔に書けます。switch v := i.(type) という特別な構文で、i の動的型に応じて case を分岐させます。各 case の中では、変数 v はその case の型として扱えるので、そのまま型固有の処理を書けます。
- 42実行可error 型 — errors.New と fmt.ErrorfGo では、関数の失敗を例外ではなく戻り値で表します。その戻り値の型が組み込みインターフェース error です。error は Error() string という一つのメソッドだけを持つインターフェースで、エラーメッセージを文字列で返せる値なら何でも error として扱えます。成功時は nil、失敗時は中身のあるエラー値を返すのが約束です。
- 43実行可エラーのラップ — %w と errors.Is / errors.Asエラーを上位へ返すとき、どこで何をしていて失敗したのかという文脈を足すと原因を追いやすくなります。fmt.Errorf の中で %w 動詞を使うと、元のエラーを包み込んだ(ラップした)新しいエラーを作れます。表示すると「外側の説明: 内側のエラー」のように連なり、元のエラーは内部に保持されたままになります。
- 44実行可fmt.Stringer — 表示方法を自分で定義するfmt パッケージは、表示する値が String() string というメソッドを持っていれば、それを呼んで文字列化します。このメソッドを定める標準インターフェースが fmt.Stringer です。自分の型に String() メソッドを実装すると、fmt.Println や %v での表示を自分好みの見た目に変えられます。
並行処理
- 45実行可goroutine — go キーワードで並行実行goroutine は Go のランタイムが管理する軽量なスレッドです。関数呼び出しの前に go キーワードを付けるだけで、その関数は新しい goroutine として並行に実行され、呼び出し側はその完了を待たずに次の行へ進みます。OS スレッドより圧倒的に軽いため、何千・何万と起動しても問題ありません。
- 46実行可チャネル — goroutine 間で値をやりとりするチャネル(channel)は goroutine 同士が値を安全に送受信するための通り道です。make(chan 型) で作り、ch <- v で値を送信し、v := <-ch で受信します。矢印はデータが流れる向きを表すと覚えると分かりやすいです。
- 47実行可バッファ付きチャネル — 容量を持つチャネルmake(chan int, 3) のように第2引数で容量を指定すると、バッファ付きチャネルになります。バッファに空きがある間は、受信側がいなくても送信がブロックせずに進みます。バッファが満杯になると、空きができるまで送信側がブロックします。
- 48実行可チャネルの方向 — 送信専用と受信専用関数の引数としてチャネルを渡すとき、その関数が送信だけ・受信だけを行うことを型で明示できます。chan<- int は送信専用、<-chan int は受信専用を表します。矢印の位置が chan の前後どちらにあるかで向きが決まります。
- 49実行可close と range — チャネルを閉じて受信を終える送信側はもう値を送らないことを close(ch) で受信側に伝えられます。閉じたチャネルからの受信は、残っている値をすべて取り出した後はゼロ値を返します。v, ok := <-ch の ok が false になることでも閉じたと判定できます。
- 50実行可select — 複数のチャネルを同時に待つselect はいくつかのチャネル操作を並べて、準備ができたものを1つ選んで実行する文です。switch に似た見た目ですが、各 case はチャネルの送信または受信です。複数の case が同時に準備できた場合は、その中からランダムに1つ選ばれます。
- 51実行可sync.WaitGroup — 複数の goroutine の完了を待つsync.WaitGroup は、起動した複数の goroutine がすべて終わるのを待つためのカウンタ付きの仕組みです。Add(n) で待つ数を増やし、各 goroutine が終了時に Done() を呼んでカウンタを1減らし、Wait() がカウンタが0になるまでブロックします。
- 52実行可sync.Mutex — 共有データを排他制御で守る複数の goroutine が同じ変数を同時に書き換えると、結果が壊れる「データ競合」が起きます。sync.Mutex(相互排他ロック)は、ある一瞬に1つの goroutine だけがその領域に入れるようにする錠前です。Lock() でロックを取り、Unlock() で解放します。
- 53実行可sync.Once と atomic — 一度きりの実行と原子的な操作sync.Once は、何度呼ばれても中の関数を一度だけ実行する仕組みです。once.Do(f) を複数の goroutine から呼んでも、f が走るのは最初の1回だけで、残りは f の完了を待ってから戻ります。初期化処理を確実に1回だけ行いたいときに便利です。
- 54実行可context — キャンセルとタイムアウトを伝えるcontext パッケージは、処理のキャンセルや締め切り(デッドライン)を、関わる goroutine の連鎖に伝えるための仕組みです。慣習として関数の第1引数に ctx context.Context を取り、context.WithCancel や context.WithTimeout で派生コンテキストを作ります。
ジェネリクス
- 55実行可型パラメータ — [T any] でジェネリックにするGo 1.18 から、関数や型に型パラメータを持たせるジェネリクスが導入されました。関数名の後ろに角かっこで型パラメータを書きます。func Print[T any](v T) の T が型パラメータで、any は「どんな型でもよい」を表す制約です(any は interface{} の別名です)。
- 56実行可制約 — comparable と constraints型パラメータには「どんな操作が許されるか」を表す制約(constraint)を指定します。any は何でも許す代わりに == や + などは使えません。比較(== / !=)が必要なら、組み込みの制約 comparable を使います。マップのキー型や、要素の一致を調べる関数で重宝します。
- 57実行可ジェネリックな関数 — Map と Filterジェネリクスは、コレクションを変換・絞り込みする汎用関数と相性が抜群です。入力の要素型 T と出力の要素型 U を別の型パラメータにすれば、Map のように「型を変える変換」も書けます。func Map[T, U any](s []T, f func(T) U) []U が典型です。
- 58実行可ジェネリックな型 — 型安全なスタック型にも型パラメータを持たせられます。type Stack[T any] struct { items []T } のように書くと、要素型をあとから決められる汎用コンテナになります。Stack[int] と Stack[string] は別々の型として扱われ、それぞれ型チェックが効きます。
- 59実行可~ による基底型制約と型集合制約のインターフェースには、許す型を | で並べた「型集合」を書けます。たとえば `interface { int | int64 | float64 }` は、これらのいずれかであることを要求します。これにより + や < などの演算子が使える型だけに限定できます。
- 60実行可ジェネリクスを使うべきか — 判断の指針Go 公式は「便利だからといって何でもジェネリックにしない」よう勧めています。目安は、複数の型に対してまったく同じロジックを書いていて、その違いが要素の型だけのときです。スライス・マップ・チャネルを操作する汎用関数や、汎用コンテナ(スタック・集合など)が典型的な適所です。
- 61実行可slices と maps — 標準の汎用パッケージ(Go 1.21+)Go 1.21 で、ジェネリクスを活用した slices パッケージと maps パッケージが標準ライブラリに加わりました。これまで自分で書きがちだった定番処理が、型安全な関数として用意されています。slices には Contains・Index・Sort・Max・Reverse などがあります。
標準ライブラリと実践
- 62実行可fmt — Printf の書式指定動詞fmt パッケージは文字列の整形と入出力を担います。Println は値を空白区切りで出力して改行し、Printf は書式文字列の中の「動詞(verb)」を引数で置き換えます。代表的な動詞は %d(整数)・%s(文字列)・%f(小数)・%t(真偽値)・%v(値を既定の形で)です。
- 63実行可strings — よく使う文字列操作strings パッケージには、文字列を調べたり加工したりする関数がそろっています。Contains(部分一致)・HasPrefix / HasSuffix(前後の一致)・ToUpper / ToLower(大文字小文字)・ReplaceAll(置換)・TrimSpace(前後の空白除去)などが日常的に使われます。
- 64実行可strconv — 文字列と数値の変換strconv パッケージは、文字列と数値・真偽値の相互変換を行います。Atoi は文字列を int に、Itoa は int を文字列にします。Atoi はパースに失敗するとエラーを返すため、戻り値の error を必ず確認します。Go では数値と文字列の暗黙変換がないので、この変換は頻繁に登場します。
- 65実行可time — 時刻・期間・フォーマットtime パッケージは時刻と期間を扱います。time.Time が時刻を、time.Duration が期間を表します。期間は time.Second や 3*time.Hour のように定数の掛け算で書けます。時刻の差は Sub で Duration として得られ、Add で時刻をずらせます。
- 66実行可並べ替え — sort と slices.Sortスライスの並べ替えには2つの道があります。古くからある sort パッケージは sort.Ints・sort.Strings といった専用関数や、任意の条件で並べる sort.Slice(s, less) を提供します。sort.Slice は比較関数 less(i, j) が「i 番目が j 番目より前か」を返す形です。
- 67実行可encoding/json — 構造体を JSON にする(Marshal)encoding/json パッケージは、Go の値と JSON を相互変換します。json.Marshal は値を受け取って JSON のバイト列([]byte)を返します。文字列として表示したいときは string(...) で変換します。整形して見やすく出すには json.MarshalIndent を使います。
- 68実行可encoding/json — JSON を読み込む(Unmarshal)json.Unmarshal は JSON のバイト列を Go の値に書き込みます。書き込み先は構造体などへのポインタを渡します(&v)。JSON のキーは構造体タグや、大文字小文字を無視したフィールド名照合でマッチします。対応するフィールドがない JSON のキーは単に無視されます。
- 69testing — go test でテストを書くGo は標準でテストの仕組みを備えています。ファイル名を _test.go で終わらせ、func TestXxx(t *testing.T) という形の関数を書くと、go test コマンドが自動で見つけて実行します。期待と違えば t.Errorf でエラーを記録し、致命的なら t.Fatalf でその場で打ち切ります。
- 70実行可エラー処理の実践 — 早期 return とラップGo では関数が error を最後の戻り値として返し、呼び出し側が if err != nil { return err } で逐一確認するのが基本様式です。エラーのときは早めに return して抜ける「早期 return」を徹底すると、正常系の処理が深くネストせず、まっすぐ読めるコードになります。
- 71net/http — HTTP クライアントの概要net/http パッケージは、標準ライブラリだけで HTTP クライアントとサーバーの両方を書けます。クライアント側は http.Get(url) で手早くリクエストでき、戻り値の *http.Response からステータスやボディを読みます。レスポンスのボディは io.ReadCloser なので、使い終わったら defer resp.Body.Close() で必ず閉じます。